//	VirtualDub - Video processing and capture application
//	A/V interface library
//	Copyright (C) 1998-2004 Avery Lee
//
//	This program is free software; you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation; either version 2 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program; if not, write to the Free Software
//	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

#pragma warning(disable: 4786)	// STFU

#define NO_DSHOW_STRSAFE
#include <vd2/Riza/capdriver.h>
#include <vd2/Riza/cap_dshow.h>
#include <vd2/system/vdstring.h>
#include <vd2/system/time.h>
#include <vd2/system/fraction.h>
#include <vd2/system/error.h>
#include <vd2/system/math.h>
#include <vd2/system/log.h>
#include <vd2/system/registry.h>
#include <objbase.h>
#include <dshow.h>
#include <windows.h>
#include <qedit.h>
#include <guiddef.h>
#include <dvdmedia.h>		// VIDEOINFOHEADER2
#include <ks.h>
#include <ksmedia.h>
#include <vector>

using namespace nsVDCapture;

#pragma comment(lib, "amstrmid.lib")

extern HINSTANCE g_hInst;

#ifdef _MSC_VER
	#pragma warning(disable: 4355)		// warning C4355: 'this' : used in base member initializer list
#endif

#ifdef _DEBUG
	#define DS_VERIFY(exp, msg) if (FAILED(hr = (exp))) { VDDEBUG("Failed: " msg " [%08lx : %s]\n", hr, GetDXErrorName(hr)); VDDumpFilterGraphDShow(mpGraph); TearDownGraph(); return false; } else
#else
	#define DS_VERIFY(exp, msg) if (FAILED(hr = (exp))) { VDLog(kVDLogWarning, VDStringW(L"CapDShow: Failed to build filter graph: " L##msg L"\n")); TearDownGraph(); return false; } else
#endif

///////////////////////////////////////////////////////////////////////////
//
//	fast semaphore
//
///////////////////////////////////////////////////////////////////////////

namespace {
	class VDFastSemaphore {
	public:
		VDFastSemaphore(int initial);
		~VDFastSemaphore();

		void Wait() {
			if (mValue.postdec() <= 0)
				SlowWait();
		}

		bool TryWait() {
			if (mValue.postinc() < 0) {
				++mValue;
				return false;
			}

			return true;
		}

		void Post() {
			if (mValue.postinc() < 0)
				SlowPost();
		}

	private:
		void SlowWait();
		void SlowPost();

		VDAtomicInt mValue;
		HANDLE mKernelSema;
	};

	VDFastSemaphore::VDFastSemaphore(int initial)
		: mValue(initial)
		, mKernelSema(CreateSemaphore(NULL, 1, 1, NULL))
	{
	}

	VDFastSemaphore::~VDFastSemaphore()
	{
	}

	void VDFastSemaphore::SlowWait() {
		WaitForSingleObject(mKernelSema, INFINITE);
	}

	void VDFastSemaphore::SlowPost() {
		ReleaseSemaphore(mKernelSema, 1, NULL);
	}
}

///////////////////////////////////////////////////////////////////////////
//
//	smart pointers
//
///////////////////////////////////////////////////////////////////////////

namespace {
	// New auto ptr for COM because the MS one has some really unsafe
	// overloads -- for instance, operator&() makes it a landmine if you
	// try putting it in an STL container. Also, the DDK doesn't come
	// with comsupp.lib.

	template<class T, const IID *T_IID>
	class VD_MSCOMAutoPtr {
	public:
		VD_MSCOMAutoPtr() : mp(NULL) {}

		VD_MSCOMAutoPtr(const VD_MSCOMAutoPtr& src)
			: mp(src.mp)
		{
			if (mp)
				mp->AddRef();
		}

		VD_MSCOMAutoPtr(T *p) : mp(p) {
			if (mp)
				mp->AddRef();
		}

		~VD_MSCOMAutoPtr() {
			if (mp)
				mp->Release();
		}

		VD_MSCOMAutoPtr& operator=(const VD_MSCOMAutoPtr& src) {
			T *const p = src.mp;

			if (p != mp) {
				if (p)
					p->AddRef();
				T *pOld = mp;
				mp = p;
				if (pOld)
					pOld->Release();
			}
			return *this;
		}

		VD_MSCOMAutoPtr& operator=(T *p) {
			if (mp != p) {
				if(p)
					p->AddRef();
				T *pOld = mp;
				mp = p;
				if (pOld)
					pOld->Release();
			}
			return *this;
		}

		operator T*() const {
			return mp;
		}

		T& operator*() const {
			return *mp;
		}

		T *operator->() const {
			return mp;
		}

		T **operator~() {
			if (mp) {
				mp->Release();
				mp = NULL;
			}
			return &mp;
		}

		bool operator!() const { return !mp; }
		bool operator==(T* p) const { return mp == p; }
		bool operator!=(T* p) const { return mp != p; }

		void Swap(VD_MSCOMAutoPtr& x) {
			std::swap(mp, x.mp);
		}

		HRESULT CreateInstance(const CLSID& clsid, IUnknown *pOuter = NULL, DWORD dwClsContext = CLSCTX_ALL) {
			if (mp) {
				mp->Release();
				mp = NULL;
			}

			return CoCreateInstance(clsid, pOuter, dwClsContext, *T_IID, (void **)&mp);
		}

		T *mp;
	};

	template<class T>
	bool operator==(T *p, const VD_MSCOMAutoPtr<T, &__uuidof(T)>& ap) {
		return p == ap.mp;
	}

	template<class T>
	bool operator!=(T *p, const VD_MSCOMAutoPtr<T, &__uuidof(T)>& ap) {
		return p != ap.mp;
	}

	#define I_HATE(x) typedef VD_MSCOMAutoPtr<x, &__uuidof(x)> x##Ptr

	I_HATE(IAMAnalogVideoDecoder);
	I_HATE(IAMAudioInputMixer);
	I_HATE(IAMCrossbar);
	I_HATE(IAMStreamConfig);
	I_HATE(IAMStreamControl);
	I_HATE(IAMTuner);
	I_HATE(IAMTVTuner);
	I_HATE(IAMVfwCaptureDialogs);
	I_HATE(IBaseFilter);
	I_HATE(ICaptureGraphBuilder2);
	I_HATE(IEnumFilters);
	I_HATE(IEnumMediaTypes);
	I_HATE(IEnumPins);
	I_HATE(IFilterGraph);
	I_HATE(IGraphBuilder);
	I_HATE(IMediaControl);
	I_HATE(IMediaEventEx);
	I_HATE(IMediaFilter);
	I_HATE(IMediaSample);
	I_HATE(IMoniker);
	I_HATE(IPin);
	I_HATE(IReferenceClock);
	I_HATE(ISampleGrabber);
	I_HATE(ISpecifyPropertyPages);
	I_HATE(IVideoWindow);
	I_HATE(IKsPropertySet);
	I_HATE(IClassFactory);
	I_HATE(IAMVideoProcAmp);

	#undef I_HATE
}

namespace {
	#ifdef _DEBUG
		const char *GetDXErrorName(const HRESULT hr) {
#define X(err) case err: return #err
			switch(hr) {
				X(VFW_E_INVALIDMEDIATYPE);
				X(VFW_E_INVALIDSUBTYPE);
				X(VFW_E_NEED_OWNER);
				X(VFW_E_ENUM_OUT_OF_SYNC);
				X(VFW_E_ALREADY_CONNECTED);
				X(VFW_E_FILTER_ACTIVE);
				X(VFW_E_NO_TYPES);
				X(VFW_E_NO_ACCEPTABLE_TYPES);
				X(VFW_E_INVALID_DIRECTION);
				X(VFW_E_NOT_CONNECTED);
				X(VFW_E_NO_ALLOCATOR);
				X(VFW_E_RUNTIME_ERROR);
				X(VFW_E_BUFFER_NOTSET);
				X(VFW_E_BUFFER_OVERFLOW);
				X(VFW_E_BADALIGN);
				X(VFW_E_ALREADY_COMMITTED);
				X(VFW_E_BUFFERS_OUTSTANDING);
				X(VFW_E_NOT_COMMITTED);
				X(VFW_E_SIZENOTSET);
				X(VFW_E_NO_CLOCK);
				X(VFW_E_NO_SINK);
				X(VFW_E_NO_INTERFACE);
				X(VFW_E_NOT_FOUND);
				X(VFW_E_CANNOT_CONNECT);
				X(VFW_E_CANNOT_RENDER);
				X(VFW_E_CHANGING_FORMAT);
				X(VFW_E_NO_COLOR_KEY_SET);
				X(VFW_E_NOT_OVERLAY_CONNECTION);
				X(VFW_E_NOT_SAMPLE_CONNECTION);
				X(VFW_E_PALETTE_SET);
				X(VFW_E_COLOR_KEY_SET);
				X(VFW_E_NO_COLOR_KEY_FOUND);
				X(VFW_E_NO_PALETTE_AVAILABLE);
				X(VFW_E_NO_DISPLAY_PALETTE);
				X(VFW_E_TOO_MANY_COLORS);
				X(VFW_E_STATE_CHANGED);
				X(VFW_E_NOT_STOPPED);
				X(VFW_E_NOT_PAUSED);
				X(VFW_E_NOT_RUNNING);
				X(VFW_E_WRONG_STATE);
				X(VFW_E_START_TIME_AFTER_END);
				X(VFW_E_INVALID_RECT);
				X(VFW_E_TYPE_NOT_ACCEPTED);
				X(VFW_E_SAMPLE_REJECTED);
				X(VFW_E_SAMPLE_REJECTED_EOS);
				X(VFW_E_DUPLICATE_NAME);
				X(VFW_S_DUPLICATE_NAME);
				X(VFW_E_TIMEOUT);
				X(VFW_E_INVALID_FILE_FORMAT);
				X(VFW_E_ENUM_OUT_OF_RANGE);
				X(VFW_E_CIRCULAR_GRAPH);
				X(VFW_E_NOT_ALLOWED_TO_SAVE);
				X(VFW_E_TIME_ALREADY_PASSED);
				X(VFW_E_ALREADY_CANCELLED);
				X(VFW_E_CORRUPT_GRAPH_FILE);
				X(VFW_E_ADVISE_ALREADY_SET);
				X(VFW_S_STATE_INTERMEDIATE);
				X(VFW_E_NO_MODEX_AVAILABLE);
				X(VFW_E_NO_ADVISE_SET);
				X(VFW_E_NO_FULLSCREEN);
				X(VFW_E_IN_FULLSCREEN_MODE);
				X(VFW_E_UNKNOWN_FILE_TYPE);
				X(VFW_E_CANNOT_LOAD_SOURCE_FILTER);
				X(VFW_S_PARTIAL_RENDER);
				X(VFW_E_FILE_TOO_SHORT);
				X(VFW_E_INVALID_FILE_VERSION);
				X(VFW_S_SOME_DATA_IGNORED);
				X(VFW_S_CONNECTIONS_DEFERRED);
				X(VFW_E_INVALID_CLSID);
				X(VFW_E_INVALID_MEDIA_TYPE);
				X(VFW_E_SAMPLE_TIME_NOT_SET);
				X(VFW_S_RESOURCE_NOT_NEEDED);
				X(VFW_E_MEDIA_TIME_NOT_SET);
				X(VFW_E_NO_TIME_FORMAT_SET);
				X(VFW_E_MONO_AUDIO_HW);
				X(VFW_S_MEDIA_TYPE_IGNORED);
				X(VFW_E_NO_DECOMPRESSOR);
				X(VFW_E_NO_AUDIO_HARDWARE);
				X(VFW_S_VIDEO_NOT_RENDERED);
				X(VFW_S_AUDIO_NOT_RENDERED);
				X(VFW_E_RPZA);
				X(VFW_S_RPZA);
				X(VFW_E_PROCESSOR_NOT_SUITABLE);
				X(VFW_E_UNSUPPORTED_AUDIO);
				X(VFW_E_UNSUPPORTED_VIDEO);
				X(VFW_E_MPEG_NOT_CONSTRAINED);
				X(VFW_E_NOT_IN_GRAPH);
				X(VFW_S_ESTIMATED);
				X(VFW_E_NO_TIME_FORMAT);
				X(VFW_E_READ_ONLY);
				X(VFW_S_RESERVED);
				X(VFW_E_BUFFER_UNDERFLOW);
				X(VFW_E_UNSUPPORTED_STREAM);
				X(VFW_E_NO_TRANSPORT);
				X(VFW_S_STREAM_OFF);
				X(VFW_S_CANT_CUE);
				X(VFW_E_BAD_VIDEOCD);
				X(VFW_S_NO_STOP_TIME);
				X(VFW_E_OUT_OF_VIDEO_MEMORY);
				X(VFW_E_VP_NEGOTIATION_FAILED);
				X(VFW_E_DDRAW_CAPS_NOT_SUITABLE);
				X(VFW_E_NO_VP_HARDWARE);
				X(VFW_E_NO_CAPTURE_HARDWARE);
				X(VFW_E_DVD_OPERATION_INHIBITED);
				X(VFW_E_DVD_INVALIDDOMAIN);
				X(VFW_E_DVD_NO_BUTTON);
				X(VFW_E_DVD_GRAPHNOTREADY);
				X(VFW_E_DVD_RENDERFAIL);
				X(VFW_E_DVD_DECNOTENOUGH);
				X(VFW_E_DVD_NOT_IN_KARAOKE_MODE);
				X(VFW_E_FRAME_STEP_UNSUPPORTED);
				X(VFW_E_PIN_ALREADY_BLOCKED_ON_THIS_THREAD);
				X(VFW_E_PIN_ALREADY_BLOCKED);
				X(VFW_E_CERTIFICATION_FAILURE);
				X(VFW_E_VMR_NOT_IN_MIXER_MODE);
				X(VFW_E_VMR_NO_AP_SUPPLIED);
				X(VFW_E_VMR_NO_DEINTERLACE_HW);
				X(VFW_E_VMR_NO_PROCAMP_HW);
				X(VFW_E_DVD_VMR9_INCOMPATIBLEDEC);
				X(VFW_E_BAD_KEY);
			default:
				return "";
			}
#undef X
		}
	#else
		const char *GetDXErrorName(const HRESULT hr) {
			return "";
		}
	#endif

	HRESULT AddToRot(IUnknown *pUnkGraph, DWORD *pdwRegister) 
	{
		IMoniker * pMoniker;
		IRunningObjectTable *pROT;
		if (FAILED(GetRunningObjectTable(0, &pROT))) {
			return E_FAIL;
		}
		WCHAR wsz[256];
		swprintf(wsz, 256, L"FilterGraph %08p pid %08x", (DWORD_PTR)pUnkGraph, GetCurrentProcessId());
		HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker);
		if (SUCCEEDED(hr)) {
			hr = pROT->Register(0, pUnkGraph, pMoniker, pdwRegister);
			pMoniker->Release();
		}
		pROT->Release();
		return hr;
	}

	void RemoveFromRot(DWORD pdwRegister)
	{
		IRunningObjectTable *pROT;
		if (SUCCEEDED(GetRunningObjectTable(0, &pROT))) {
			pROT->Revoke(pdwRegister);
			pROT->Release();
		}
	}

	void DestroySubgraph(IFilterGraph *pGraph, IBaseFilter *pFilt, IBaseFilter *pKeepFilter, IBaseFilter *pKeepFilter2) {
		IEnumPins *pEnum;

		if (!pFilt)
			return;

		if (SUCCEEDED(pFilt->EnumPins(&pEnum))) {
			IPin *pPin;

			pEnum->Reset();

			for(;;) {
				HRESULT hr = pEnum->Next(1, &pPin, 0);
				if (hr == VFW_E_ENUM_OUT_OF_SYNC) {
					hr = pEnum->Reset();
					if (SUCCEEDED(hr))
						continue;
				}
				if (hr != S_OK)
					break;

				PIN_DIRECTION dir;

				VDVERIFY(SUCCEEDED(pPin->QueryDirection(&dir)));

				if (dir == PINDIR_OUTPUT) {
					IPin *pPin2;

					if (SUCCEEDED(pPin->ConnectedTo(&pPin2))) {
						PIN_INFO pi;

						if (SUCCEEDED(pPin2->QueryPinInfo(&pi))) {
							DestroySubgraph(pGraph, pi.pFilter, pKeepFilter, pKeepFilter2);

							if ((!pKeepFilter || pi.pFilter != pKeepFilter) && (!pKeepFilter2 || pi.pFilter != pKeepFilter2))
								VDVERIFY(SUCCEEDED(pGraph->RemoveFilter(pi.pFilter)));

							pi.pFilter->Release();
						}

						pPin2->Release();
					}
				}

				pPin->Release();
			}

			pEnum->Release();
		}
	}

	AM_MEDIA_TYPE *RizaCopyMediaType(const AM_MEDIA_TYPE *pSrc) {
		AM_MEDIA_TYPE *pamt;

		if (pamt = (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE))) {

			*pamt = *pSrc;

			if (pamt->pbFormat = (BYTE *)CoTaskMemAlloc(pSrc->cbFormat)) {
				memcpy(pamt->pbFormat, pSrc->pbFormat, pSrc->cbFormat);

				if (pamt->pUnk)
					pamt->pUnk->AddRef();

				return pamt;
			}

			CoTaskMemFree(pamt);
		}

		return NULL;
	}

	bool RizaCopyMediaType(AM_MEDIA_TYPE *pDst, const AM_MEDIA_TYPE *pSrc) {
		*pDst = *pSrc;

		if (pDst->pbFormat = (BYTE *)CoTaskMemAlloc(pSrc->cbFormat)) {
			memcpy(pDst->pbFormat, pSrc->pbFormat, pSrc->cbFormat);

			if (pDst->pUnk)
				pDst->pUnk->AddRef();

			return true;
		}

		return false;
	}

	void RizaDeleteMediaType(AM_MEDIA_TYPE *pamt) {
		if (!pamt)
			return;

		if (pamt->pUnk)
			pamt->pUnk->Release();

		if (pamt->pbFormat)
			CoTaskMemFree(pamt->pbFormat);

		CoTaskMemFree(pamt);
	}

	bool SetClockFromDownstream(IBaseFilter *pFilt, IMediaFilter *pGraphMF) {
		IReferenceClock *pRC;
		if (SUCCEEDED(pFilt->QueryInterface(IID_IReferenceClock, (void **)&pRC))) {
			pGraphMF->SetSyncSource(pRC);
			pRC->Release();

			return true;
		}

		bool success = false;

		IEnumPins *pEnum;
		if (SUCCEEDED(pFilt->EnumPins(&pEnum))) {
			IPin *pPin;

			pEnum->Reset();

			for(;;) {
				HRESULT hr = pEnum->Next(1, &pPin, 0);
				if (hr == VFW_E_ENUM_OUT_OF_SYNC) {
					hr = pEnum->Reset();
					if (SUCCEEDED(hr))
						continue;
				}
				if (hr != S_OK)
					break;

				PIN_DIRECTION dir;

				VDVERIFY(SUCCEEDED(pPin->QueryDirection(&dir)));

				if (dir == PINDIR_OUTPUT) {
					IPin *pPin2;

					if (SUCCEEDED(pPin->ConnectedTo(&pPin2))) {
						PIN_INFO pi;

						if (SUCCEEDED(pPin2->QueryPinInfo(&pi))) {
							if (SetClockFromDownstream(pi.pFilter, pGraphMF))
								success = true;

							pi.pFilter->Release();
						}

						pPin2->Release();
					}
				}

				pPin->Release();

				if (success)
					break;
			}

			pEnum->Release();
		}

		return success;
	}

	typedef std::vector<std::pair<IMonikerPtr, VDStringW> > tDeviceVector;

	void Enumerate(tDeviceVector& devlist, REFCLSID devclsid) {
		ICreateDevEnum *pCreateDevEnum;
		IEnumMoniker *pEm = NULL;

		if (SUCCEEDED(CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pCreateDevEnum))) {
			pCreateDevEnum->CreateClassEnumerator(devclsid, &pEm, 0);
			pCreateDevEnum->Release();
		}

		if (pEm) {
			IMoniker *pM;
			ULONG cFetched;

			while(S_OK == pEm->Next(1, &pM, &cFetched) && cFetched==1) {
				IPropertyBag *pPropBag;

				if (SUCCEEDED(pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag))) {
					VARIANT varName;

					varName.vt = VT_BSTR;
					varName.bstrVal = NULL;

					if (SUCCEEDED(pPropBag->Read(L"FriendlyName", &varName, 0))) {
						VDStringW name(varName.bstrVal);

						if (devclsid == CLSID_VideoInputDeviceCategory) {
							bool isVFWDriver = false;
							LPOLESTR displayName;
							if (SUCCEEDED(pM->GetDisplayName(NULL, NULL, &displayName))) {
								// Detect a VFW driver by the compression manager tag.
								if (!wcsncmp(displayName, L"@device:cm:", 11))
									isVFWDriver = true;
								CoTaskMemFree(displayName);
							}
							name += (isVFWDriver ? L" (VFW>DirectShow)" : L" (DirectShow)");
						}

						devlist.push_back(tDeviceVector::value_type(pM, name));

						SysFreeString(varName.bstrVal);
					}

					pPropBag->Release();
				}

				pM->Release();
			}

			pEm->Release();
		}
	}

	class VDWaveFormatAsDShowMediaType : public AM_MEDIA_TYPE {
	public:
		VDWaveFormatAsDShowMediaType(const WAVEFORMATEX *pwfex, UINT size) {
			majortype		= MEDIATYPE_Audio;
			subtype.Data1	= pwfex->wFormatTag;
			subtype.Data2	= 0;
			subtype.Data3	= 0x0010;
			subtype.Data4[0] = 0x80;
			subtype.Data4[1] = 0x00;
			subtype.Data4[2] = 0x00;
			subtype.Data4[3] = 0xAA;
			subtype.Data4[4] = 0x00;
			subtype.Data4[5] = 0x38;
			subtype.Data4[6] = 0x9B;
			subtype.Data4[7] = 0x71;
			bFixedSizeSamples	= TRUE;
			bTemporalCompression	= FALSE;
			lSampleSize		= pwfex->nBlockAlign;
			formattype		= FORMAT_WaveFormatEx;
			pUnk			= NULL;
			cbFormat		= size;
			pbFormat		= (BYTE *)pwfex;
		}
	};

	class VDAMMediaType : public AM_MEDIA_TYPE {
	public:
		VDAMMediaType() {
			cbFormat = 0;
			pbFormat = NULL;
		}

		VDAMMediaType(const VDAMMediaType& src) {
			*static_cast<AM_MEDIA_TYPE *>(this) = src;

			if (pbFormat) {
				pbFormat = (BYTE *)CoTaskMemAlloc(cbFormat);
				if (pbFormat)
					memcpy(pbFormat, src.pbFormat, cbFormat);
				else
					cbFormat = 0;
			} else
				cbFormat = 0;
		}

		VDAMMediaType(const AM_MEDIA_TYPE& src) {
			*static_cast<AM_MEDIA_TYPE *>(this) = src;

			if (pbFormat) {
				pbFormat = (BYTE *)CoTaskMemAlloc(cbFormat);
				if (pbFormat)
					memcpy(pbFormat, src.pbFormat, cbFormat);
				else
					cbFormat = 0;
			} else
				cbFormat = 0;
		}

		~VDAMMediaType() {
			if (pbFormat)
				CoTaskMemFree(pbFormat);
		}

		VDAMMediaType& operator=(const VDAMMediaType& src) {
			if (pbFormat)
				CoTaskMemFree(pbFormat);

			*static_cast<AM_MEDIA_TYPE *>(this) = src;

			if (pbFormat) {
				pbFormat = (BYTE *)CoTaskMemAlloc(cbFormat);
				if (pbFormat)
					memcpy(pbFormat, src.pbFormat, cbFormat);
				else
					cbFormat = 0;
			} else
				cbFormat = 0;

			return *this;
		}

		VDAMMediaType& operator=(const AM_MEDIA_TYPE& src) {
			if (pbFormat)
				CoTaskMemFree(pbFormat);

			*static_cast<AM_MEDIA_TYPE *>(this) = src;

			if (pbFormat) {
				pbFormat = (BYTE *)CoTaskMemAlloc(cbFormat);
				if (pbFormat)
					memcpy(pbFormat, src.pbFormat, cbFormat);
				else
					cbFormat = 0;
			} else
				cbFormat = 0;

			return *this;
		}

		void clear() {
			if (pbFormat) {
				CoTaskMemFree(pbFormat);
				pbFormat = NULL;
				cbFormat = 0;
			}

			majortype = GUID_NULL;
			subtype = GUID_NULL;
			formattype = GUID_NULL;
		}

		void *realloc(size_t n) {
			if (cbFormat != n) {
				if (pbFormat) {
					CoTaskMemFree(pbFormat);
					pbFormat = NULL;
					cbFormat = 0;
				}

				pbFormat = (BYTE *)CoTaskMemAlloc(n);
				if (pbFormat)
					cbFormat = n;
				else
					cbFormat = 0;
			}

			return pbFormat;
		}
	};

	const wchar_t *VDGetNameForPhysicalConnectorTypeDShow(PhysicalConnectorType type) {
		switch(type) {
		case PhysConn_Video_Tuner:				return L"Video Tuner";
		case PhysConn_Video_Composite:			return L"Video Composite";
		case PhysConn_Video_SVideo:				return L"Video SVideo";
		case PhysConn_Video_RGB:				return L"Video RGB";
		case PhysConn_Video_YRYBY:				return L"Video YRYBY";
		case PhysConn_Video_SerialDigital:		return L"Video Serial Digital";
		case PhysConn_Video_ParallelDigital:	return L"Video Parallel Digital";
		case PhysConn_Video_SCSI:				return L"Video SCSI";
		case PhysConn_Video_AUX:				return L"Video AUX";
		case PhysConn_Video_1394:				return L"Video 1394";
		case PhysConn_Video_USB:				return L"Video USB";
		case PhysConn_Video_VideoDecoder:		return L"Video Decoder";
		case PhysConn_Video_VideoEncoder:		return L"Video Encoder";
		case PhysConn_Video_SCART:				return L"Video SCART";

		case PhysConn_Audio_Tuner:				return L"Audio Tuner";
		case PhysConn_Audio_Line:				return L"Audio Line";
		case PhysConn_Audio_Mic:				return L"Audio Mic";
		case PhysConn_Audio_AESDigital:			return L"Audio AES Digital";
		case PhysConn_Audio_SPDIFDigital:		return L"Audio SPDIF Digital";
		case PhysConn_Audio_SCSI:				return L"Audio SCSI";
		case PhysConn_Audio_AUX:				return L"Audio AUX";
		case PhysConn_Audio_1394:				return L"Audio 1394";
		case PhysConn_Audio_USB:				return L"Audio USB";
		case PhysConn_Audio_AudioDecoder:		return L"Audio Decoder";

		default:								return L"(Unknown type)";
		}
	}

	bool VDIsPinConnectedDShow(IPin *pPin) {
		IPinPtr pConn;
		return SUCCEEDED(pPin->ConnectedTo(~pConn));
	}

	bool VDGetFilterConnectedToPinDShow(IPin *pPin, IBaseFilter **ppFilter) {
		IPinPtr pConn;
		if (FAILED(pPin->ConnectedTo(~pConn)))
			return false;

		PIN_INFO pi;
		if (FAILED(pConn->QueryPinInfo(&pi)))
			return false;

		*ppFilter = pi.pFilter;
		return true;
	}

	bool VDIsFilterInGraphDShow(IBaseFilter *pFilter) {
		FILTER_INFO fi;

		if (FAILED(pFilter->QueryFilterInfo(&fi)))
			return false;

		bool inGraph = fi.pGraph != NULL;

		if (fi.pGraph)
			fi.pGraph->Release();

		return inGraph;
	}

	void VDDumpFilterGraphDShow(IFilterGraph *pGraph) {
		std::list<IBaseFilterPtr> filters;

		IEnumFiltersPtr pEnumFilters;
		if (SUCCEEDED(pGraph->EnumFilters(~pEnumFilters))) {
			for(;;) {
				IBaseFilterPtr pFilter;
				HRESULT hr = pEnumFilters->Next(1, ~pFilter, NULL);
				if (hr == VFW_E_ENUM_OUT_OF_SYNC) {
					filters.clear();
					if (FAILED(pEnumFilters->Reset()))
						break;
					continue;
				}

				if (hr != S_OK)
					break;

				filters.push_back(pFilter);
			}
			pEnumFilters = NULL;
		}

		VDDEBUG("Filter graph %p:\n", &*pGraph);

		IMediaFilterPtr pGraphMF;
		if (SUCCEEDED(pGraph->QueryInterface(IID_IMediaFilter, (void **)~pGraphMF))) {
			IReferenceClockPtr pRefClock;

			if (SUCCEEDED(pGraphMF->GetSyncSource(~pRefClock))) {
				IBaseFilterPtr pRefFilter;
				if (!pRefClock)
					VDDEBUG("  Reference clock: none\n");
				else if (SUCCEEDED(pRefClock->QueryInterface(IID_IFilterGraph, (void **)~IFilterGraphPtr())))
					VDDEBUG("  Reference clock: filter graph\n");
				else if (SUCCEEDED(pRefClock->QueryInterface(IID_IBaseFilter, (void **)~pRefFilter))) {
					FILTER_INFO fi;

					if (SUCCEEDED(pRefFilter->QueryFilterInfo(&fi))) {
						if (fi.pGraph)
							fi.pGraph->Release();

						VDDEBUG("  Reference clock: filter \"%ls\"\n", fi.achName);
					}
				} else if (pRefClock)
					VDDEBUG("  Reference clock: unknown\n");
			}
		}

		while(!filters.empty()) {
			IBaseFilterPtr pFilter;

			pFilter.Swap(filters.front());
			filters.pop_front();

			FILTER_INFO fi;
			fi.achName[0] = 0;
			if (SUCCEEDED(pFilter->QueryFilterInfo(&fi))) {
				fi.pGraph->Release();
			}
			VDDEBUG("  Filter %p [%ls]:\n", &*pFilter, fi.achName);

			IEnumPinsPtr pEnumPins;
			if (SUCCEEDED(pFilter->EnumPins(~pEnumPins))) {
				for(;;) {
					IPinPtr pPin;
					HRESULT hr = pEnumPins->Next(1, ~pPin, NULL);
					if (hr != S_OK)
						break;

					PIN_INFO pi;
					VDVERIFY(SUCCEEDED(pPin->QueryPinInfo(&pi)));
					pi.pFilter->Release();
					VDDEBUG("    Pin \"%ls\" (%s): ", pi.achName, pi.dir == PINDIR_INPUT ? "input " : "output");

					IPinPtr pPinConn;
					if (SUCCEEDED(pPin->ConnectedTo(~pPinConn))) {
						PIN_INFO pi2;
						FILTER_INFO fi2;
						VDVERIFY(SUCCEEDED(pPinConn->QueryPinInfo(&pi2)));
						VDVERIFY(SUCCEEDED(pi2.pFilter->QueryFilterInfo(&fi2)));
						VDDEBUG(" connected to pin \"%ls\" of filter \"%ls\"\n", pi2.achName, fi2.achName);
						fi2.pGraph->Release();
						pi2.pFilter->Release();
					} else
						VDDEBUG(" unconnected\n");
				}
			}
		}
		VDDEBUG("\n");
	}

	bool VDSaveFilterGraphDShow(const wchar_t *filename, IFilterGraph *pGraph) {
		bool success = false;

		IStorage *pStorage = NULL;
		HRESULT hr = StgCreateDocfile(filename, STGM_CREATE | STGM_TRANSACTED | STGM_WRITE | STGM_SHARE_DENY_READ, 0, &pStorage);
		if (SUCCEEDED(hr)) {
			IPersistStream *pPersistStream = NULL;
			hr = pGraph->QueryInterface(IID_IPersistStream, (void **)&pPersistStream);
			if (SUCCEEDED(hr)) {
				IStream *pStream = NULL;
				hr = pStorage->CreateStream(L"ActiveMovieGraph", STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream);
				if(SUCCEEDED(hr)) {
					hr = pPersistStream->Save(pStream, TRUE);
					success = SUCCEEDED(hr);
					pStream->Release();
				}
				pPersistStream->Release();
			}
			pStorage->Commit(STGC_DEFAULT);
			pStorage->Release();
		}
		return success;
	}
}

///////////////////////////////////////////////////////////////////////////
//
//	DirectShow sample callbacks.
//
//	We place these in separate classes because we need two of them, and
//	that means we can't place it in the device. *sigh*
//
///////////////////////////////////////////////////////////////////////////

class IVDCaptureDSCallback {
public:
	virtual bool CapTryEnterCriticalSection() = 0;
	virtual void CapProcessData(int stream, const void *data, uint32 size, sint64 timestamp, bool key) = 0;
	virtual void CapLeaveCriticalSection() = 0;
};

class VDCapDevDSCallback : public ISampleGrabberCB {
protected:

	VDAtomicInt mRefCount;
	IVDCaptureDSCallback *mpCallback;

public:

	VDCapDevDSCallback(IVDCaptureDSCallback *pCB) : mRefCount(1), mpCallback(pCB) {
	}

	// IUnknown

	HRESULT __stdcall QueryInterface(REFIID iid, void **ppvObject) {
		if (iid == IID_IUnknown) {
			*ppvObject = static_cast<IUnknown *>(this);
			AddRef();
			return S_OK;
		} else if (iid == IID_ISampleGrabberCB) {
			*ppvObject = static_cast<ISampleGrabberCB *>(this);
			AddRef();
			return S_OK;
		}

		*ppvObject = NULL;
		return E_NOINTERFACE;
	}

	ULONG __stdcall AddRef() {
		return ++mRefCount;
	}

	ULONG __stdcall Release() {
		int rv = --mRefCount;

		if (!rv)
			delete this;

		return (ULONG)rv;
	}

	// ISampleGrabberCB

	HRESULT __stdcall BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen) {
		return E_FAIL;
	}
};

class VDCapDevDSVideoCallback : public VDCapDevDSCallback {
public:
	VDCapDevDSVideoCallback(IVDCaptureDSCallback *pCB)
		: VDCapDevDSCallback(pCB)
		, mChannel(0)
	{
	}

	void SetChannel(int chan) { mChannel = chan; }
	void SetFrameCount(int count) { mFrameCount = count; }
	int GetFrameCount() const { return mFrameCount; }

	HRESULT __stdcall SampleCB(double SampleTime, IMediaSample *pSample) {
		BYTE *pData;
		HRESULT hr;

		if (mpCallback->CapTryEnterCriticalSection()) {
			// retrieve sample pointer
			hr = pSample->GetPointer(&pData);
			if (FAILED(hr))
				return hr;

			// retrieve times
			__int64 t1, t2;
			hr = pSample->GetTime(&t1, &t2);
			if (FAILED(hr))
				t1 = t2 = -1;
			else
				t1 = (t1+5)/10;

			mpCallback->CapProcessData(mChannel, pData, pSample->GetActualDataLength(), t1, S_OK == pSample->IsSyncPoint());
			++mFrameCount;

			mpCallback->CapLeaveCriticalSection();
		}

		return S_OK;
	}

protected:
	int mChannel;
	VDAtomicInt	mFrameCount;
};

class VDCapDevDSAudioCallback : public VDCapDevDSCallback {
public:
	VDCapDevDSAudioCallback(IVDCaptureDSCallback *pCB)
		: VDCapDevDSCallback(pCB)
		, mChannel(1)
	{
	}

	void SetChannel(int chan) { mChannel = chan; }

	HRESULT __stdcall SampleCB(double SampleTime, IMediaSample *pSample) {
		BYTE *pData;
		HRESULT hr;

		if (mpCallback->CapTryEnterCriticalSection()) {
			// retrieve sample pointer
			hr = pSample->GetPointer(&pData);
			if (FAILED(hr))
				return hr;

			// Retrieve times. Note that if no clock is set, this will fail.
			__int64 t1, t2;
			hr = pSample->GetTime(&t1, &t2);
			if (FAILED(hr))
				t1 = t2 = -1;
			else
				t1 = (t1+5)/10;

			mpCallback->CapProcessData(mChannel, pData, pSample->GetActualDataLength(), t1, S_OK == pSample->IsSyncPoint());
			mpCallback->CapLeaveCriticalSection();
		}

		return S_OK;
	}

protected:
	int mChannel;
};

///////////////////////////////////////////////////////////////////////////
//
//	capture driver: DirectShow
//
///////////////////////////////////////////////////////////////////////////

class VDCaptureDriverDS : public IVDCaptureDriver, public IVDCaptureDriverDShow, public IVDCaptureDSCallback {
	VDCaptureDriverDS(const VDCaptureDriverDS&);
	VDCaptureDriverDS& operator=(const VDCaptureDriverDS&);
public:
	VDCaptureDriverDS(IMoniker *pVideoDevice);
	~VDCaptureDriverDS();

	void	*AsInterface(uint32 id);

	bool	Init(VDGUIHandle hParent);
	void	Shutdown();

	void	SetCallback(IVDCaptureDriverCallback *pCB);

	void	LockUpdates();
	void	UnlockUpdates();

	bool	IsHardwareDisplayAvailable();

	void	SetDisplayMode(nsVDCapture::DisplayMode m);
	nsVDCapture::DisplayMode		GetDisplayMode();

	void	SetDisplayRect(const vdrect32& r);
	vdrect32	GetDisplayRectAbsolute();
	void	SetDisplayVisibility(bool vis);

	void	SetFramePeriod(sint32 framePeriod100nsUnits);
	sint32	GetFramePeriod();

	uint32	GetPreviewFrameCount();

	bool	GetVideoFormat(vdstructex<BITMAPINFOHEADER>& vformat);
	bool	SetVideoFormat(const BITMAPINFOHEADER *pbih, uint32 size);

	bool	SetTunerChannel(int channel);
	int		GetTunerChannel();
	bool	GetTunerChannelRange(int& minChannel, int& maxChannel);
	uint32	GetTunerFrequencyPrecision();
	uint32	GetTunerExactFrequency();
	bool	SetTunerExactFrequency(uint32 freq);
	nsVDCapture::TunerInputMode	GetTunerInputMode();
	void	SetTunerInputMode(nsVDCapture::TunerInputMode tunerMode);

	int		GetAudioDeviceCount();
	const wchar_t *GetAudioDeviceName(int idx);
	bool	SetAudioDevice(int idx);
	int		GetAudioDeviceIndex();
	bool	IsAudioDeviceIntegrated(int idx);

	int		GetVideoSourceCount();
	const wchar_t *GetVideoSourceName(int idx);
	bool	SetVideoSource(int idx);
	int		GetVideoSourceIndex();

	int		GetAudioSourceCount();
	const wchar_t *GetAudioSourceName(int idx);
	bool	SetAudioSource(int idx);
	int		GetAudioSourceIndex();

	int		GetAudioSourceForVideoSource(int idx);

	int		GetAudioInputCount();
	const wchar_t *GetAudioInputName(int idx);
	bool	SetAudioInput(int idx);
	int		GetAudioInputIndex();

	bool	IsAudioCapturePossible();
	bool	IsAudioCaptureEnabled();
	bool	IsAudioPlaybackPossible();
	bool	IsAudioPlaybackEnabled();
	void	SetAudioCaptureEnabled(bool b);
	void	SetAudioAnalysisEnabled(bool b);
	void	SetAudioPlaybackEnabled(bool b);

	void	GetAvailableAudioFormats(std::list<vdstructex<WAVEFORMATEX> >& aformats);

	bool	GetAudioFormat(vdstructex<WAVEFORMATEX>& aformat);
	bool	SetAudioFormat(const WAVEFORMATEX *pwfex, uint32 size);

	bool	IsDriverDialogSupported(nsVDCapture::DriverDialog dlg);
	void	DisplayDriverDialog(nsVDCapture::DriverDialog dlg);

	bool	IsPropertySupported(uint32 id);
	sint32	GetPropertyInt(uint32 id, bool *pAutomatic);
	void	SetPropertyInt(uint32 id, sint32 value, bool automatic);
	void	GetPropertyInfoInt(uint32 id, sint32& minVal, sint32& maxVal, sint32& step, sint32& defaultVal, bool& automatic, bool& manual);

	bool	CaptureStart();
	void	CaptureStop();
	void	CaptureAbort();

public:
	bool	GetDisableClockForPreview();
	void	SetDisableClockForPreview(bool enabled);

	bool	GetForceAudioRendererClock();
	void	SetForceAudioRendererClock(bool enabled);

protected:
	struct InputSource;
	typedef std::vector<InputSource>	InputSources;

	void	UpdateDisplay();
	bool	StartGraph();
	bool	StopGraph();
	bool	BuildPreviewGraph();
	bool	BuildCaptureGraph();
	bool	BuildGraph(bool bNeedCapture, bool bEnableAudio);
	void	TearDownGraph();
	void	CheckForWindow();
	void	CheckForChanges();
	bool	DisplayPropertyPages(IUnknown *ptr, HWND hwndParent, const GUID *pDisablePages, int nDisablePages);
	int		EnumerateCrossbarSources(InputSources& sources, IAMCrossbar *pCrossbar, int output);
	int		UpdateCrossbarSource(InputSources& sources, IAMCrossbar *pCrossbar, int output);
	void	DoEvents();

	static LRESULT CALLBACK StaticMessageSinkWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
	LRESULT MessageSinkWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

	bool CapTryEnterCriticalSection();
	void CapProcessData(int stream, const void *data, uint32 size, sint64 timestamp, bool key);
	void CapLeaveCriticalSection();

	IVDCaptureDriverCallback	*mpCB;
	DisplayMode			mDisplayMode;

	typedef std::vector<std::pair<IMonikerPtr, VDStringW> > tDeviceVector;

	IMonikerPtr			mVideoDeviceMoniker;
	tDeviceVector		mAudioDevices;
	int					mAudioDeviceIndex;

	// Some essentials for the filter graph.
	IFilterGraphPtr				mpGraph;
	IGraphBuilderPtr			mpGraphBuilder;
	ICaptureGraphBuilder2Ptr	mpCapGraphBuilder2;
	IMediaControlPtr			mpGraphControl;
	IMediaEventExPtr			mpMediaEventEx;

	// Pointers to filters and pins in the graph.
	IBaseFilterPtr		mpCapFilt;
	IBaseFilterPtr		mpCapSplitFilt;			// DV Splitter, if INTERLEAVED is detected
	IBaseFilterPtr		mpCapTransformFilt;		//
	IPinPtr				mpShadowedRealCapturePin;		// the one on the cap filt
	IPinPtr				mpRealCapturePin;		// the one on the cap filt, or a transform filter if present
	IPinPtr				mpRealPreviewPin;		// the one on the cap filt
	IPinPtr				mpRealAudioPin;			// the one on the cap filt
	IPinPtr				mpCapFiltVideoPortPin;	// on cap filt
	IPinPtr				mpAudioPin;
	IAMAnalogVideoDecoderPtr mpAnalogVideoDecoder;
	IAMCrossbarPtr		mpCrossbar;
	IAMCrossbarPtr		mpCrossbar2;
	IAMTunerPtr			mpTuner;
	IAMTVTunerPtr		mpTVTuner;
	IAMVideoProcAmpPtr	mpVideoProcAmp;
	IVideoWindowPtr		mpVideoWindow;
	typedef std::list<IVideoWindowPtr> VideoWindows;
	VideoWindows		mVideoWindows;
	IQualProp			*mpVideoQualProp;

	typedef std::list<IBaseFilterPtr> ExtraFilters;
	ExtraFilters		mExtraFilters;

	// Video formats.
	typedef std::list<VDAMMediaType> VideoFormats;
	VideoFormats mPreferredVideoFormats;

	// Audio filter.  We may not have one of these, actually.
	IBaseFilterPtr		mpAudioCapFilt;

	// Audio inputs
	struct InputSource {
		int						mCrossbarPin;
		int						mPhysicalType;
		VDStringW				mName;

		InputSource(int crossbarInputPin, int physType, const VDStringW& name) : mCrossbarPin(crossbarInputPin), mPhysicalType(physType), mName(name) {}
	};

	struct AudioInput {
		IAMAudioInputMixerPtr	mMixerEnable;
		VDStringW				mName;

		AudioInput(IAMAudioInputMixer *pMixerEnable, const VDStringW& name) : mMixerEnable(pMixerEnable), mName(name) {}
	};

	typedef std::vector<AudioInput>	AudioInputs;

	InputSources	mVideoSources;
	int				mCurrentVideoSource;
	InputSources	mAudioSources;
	int				mCurrentAudioSource;
	AudioInputs	mAudioInputs;
	int			mCurrentAudioInput;
	IAMCrossbar	*mpAudioCrossbar;		// This aliases either mpCrossbar or mpCrossbar2.
	IAMCrossbar	*mpVideoCrossbar;		// This aliases either mpCrossbar or mpCrossbar2.
	int			mAudioCrossbarOutput;
	int			mVideoCrossbarOutput;

	// These have to be nullified when we destroy parts of the graph.
	ISampleGrabberPtr	mpVideoGrabber;
	ISampleGrabberPtr	mpAudioGrabber;

	IAMVfwCaptureDialogsPtr		mpVFWDialogs;
	IAMStreamConfigPtr			mpVideoConfigCap;
	IAMStreamConfigPtr			mpVideoConfigPrv;
	IAMStreamConfigPtr			mpAudioConfig;

	// Callbacks
	VDCapDevDSVideoCallback		mVideoCallback;
	VDCapDevDSAudioCallback		mAudioCallback;
	VDFastSemaphore				mGraphStateLock;

	// Misc flags & state
	DWORD mdwRegisterGraph;				// Used to register filter graph in ROT for graphedit
	HWND mhwndParent;					// Parent window
	HWND mhwndEventSink;				// Our dummy window used as DS event sink
	vdrect32 mDisplayRect;

	uint32	mUpdateLocks;
	bool	mbUpdatePending;
	bool	mbStartPending;

	bool mbAudioCaptureEnabled;
	bool mbAudioAnalysisEnabled;
	bool mbAudioPlaybackEnabled;
	bool mbGraphActive;					// true if the graph is currently running
	bool mbGraphHasPreview;				// true if the graph has separate capture and preview pins
	bool mbDisplayVisible;
	bool mbForceAudioRendererClock;		// force the audio renderer to be the clock when present
	bool mbDisableClockForPreview;		// disable the clock by default

	// state tracking for reporting changes
	sint32		mTrackedFramePeriod;
	vdstructex<BITMAPINFOHEADER>	mTrackedVideoFormat;

	vdstructex<WAVEFORMATEX>	mAudioFormat;

	VDTime	mCaptureStart;

	VDAtomicInt	mCaptureStopQueued;

	HANDLE	mCaptureThread;
	VDAtomicPtr<MyError>	mpCaptureError;

	static ATOM sMsgSinkClass;
};

ATOM VDCaptureDriverDS::sMsgSinkClass;

VDCaptureDriverDS::VDCaptureDriverDS(IMoniker *pVideoDevice)
	: mpCB(NULL)
	, mDisplayMode(kDisplayNone)
	, mVideoDeviceMoniker(pVideoDevice)
	, mAudioDeviceIndex(0)
	, mpVideoQualProp(NULL)
	, mdwRegisterGraph(0)
	, mCurrentVideoSource(-1)
	, mCurrentAudioSource(-1)
	, mCurrentAudioInput(-1)
	, mpAudioCrossbar(NULL)
	, mpVideoCrossbar(NULL)
	, mVideoCallback(this)
	, mAudioCallback(this)
	, mGraphStateLock(1)
	, mhwndParent(NULL)
	, mhwndEventSink(NULL)
	, mDisplayRect(0,0,0,0)
	, mUpdateLocks(0)
	, mbUpdatePending(false)
	, mbStartPending(false)
	, mbAudioCaptureEnabled(true)
	, mbAudioAnalysisEnabled(false)
	, mbAudioPlaybackEnabled(false)
	, mbGraphActive(false)
	, mbGraphHasPreview(false)
	, mbDisplayVisible(false)
	, mbDisableClockForPreview(false)
	, mbForceAudioRendererClock(true)
	, mTrackedFramePeriod(0)
	, mCaptureStart(0)
	, mCaptureStopQueued(0)
	, mCaptureThread(NULL)
	, mpCaptureError(NULL)
{
}

VDCaptureDriverDS::~VDCaptureDriverDS() {
	Shutdown();
}

void *VDCaptureDriverDS::AsInterface(uint32 id) {
	if (id == IVDCaptureDriverDShow::kTypeID)
		return static_cast<IVDCaptureDriverDShow *>(this);
	return NULL;
}

void VDCaptureDriverDS::SetCallback(IVDCaptureDriverCallback *pCB) {
	mpCB = pCB;
}

bool VDCaptureDriverDS::Init(VDGUIHandle hParent) {
	mhwndParent = (HWND)hParent;

	HRESULT hr;

	if (!sMsgSinkClass) {
		WNDCLASS wc = { 0, StaticMessageSinkWndProc, 0, sizeof(void *), g_hInst, NULL, NULL, NULL, NULL, "Riza DirectShow event sink" };

		sMsgSinkClass = RegisterClass(&wc);

		if (!sMsgSinkClass)
			return false;
	}

	// Create message sink.

	if (!(mhwndEventSink = CreateWindow((LPCTSTR)sMsgSinkClass, "", WS_POPUP, 0, 0, 0, 0, mhwndParent, NULL, g_hInst, this)))
		return false;

	// Create a filter graph manager.
	DS_VERIFY(mpGraph.CreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER), "create filter graph manager");
	DS_VERIFY(mpGraph->QueryInterface(IID_IGraphBuilder, (void **)~mpGraphBuilder), "find graph builder if");

	// Create a capture filter graph builder (we're lazy).

	DS_VERIFY(mpCapGraphBuilder2.CreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER), "create filter graph builder");

	mpCapGraphBuilder2->SetFiltergraph(mpGraphBuilder);

	AddToRot(mpGraphBuilder, &mdwRegisterGraph);

	VDDEBUG("ROT entry: %x   PID: %x\n", mdwRegisterGraph, GetCurrentProcessId());

	// Try to find the event sink interface.
	if (SUCCEEDED(mpGraphBuilder->QueryInterface(IID_IMediaEventEx,(void **)~mpMediaEventEx))) {
		mpMediaEventEx->SetNotifyWindow((OAHWND)mhwndEventSink, WM_APP, 0);
		mpMediaEventEx->CancelDefaultHandling(EC_VIDEO_SIZE_CHANGED);
	}

	// Attempt to instantiate the capture filter.

	DS_VERIFY(mVideoDeviceMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void **)~mpCapFilt), "create capture filter");
	DS_VERIFY(mpGraphBuilder->AddFilter(mpCapFilt, L"Capture device"), "add capture filter");

	// Find the capture pin first.  If we don't have one of these, we
	// might as well give up.
	bool interleaved = true;

	hr = mpCapGraphBuilder2->FindPin(mpCapFilt, PINDIR_OUTPUT, &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, TRUE, 0, ~mpRealCapturePin);
	if (SUCCEEDED(hr)) {
		// We got one, so attach a DV splitter now.
		DS_VERIFY(mpCapSplitFilt.CreateInstance(CLSID_DVSplitter, NULL, CLSCTX_INPROC_SERVER), "create DV splitter");
		DS_VERIFY(mpGraphBuilder->AddFilter(mpCapSplitFilt, L"DV splitter"), "add DV splitter");

		IPinPtr pCapSplitIn;
		IPinPtr pCapSplitOutVideo;
		IPinPtr pCapSplitOutAudio;
		DS_VERIFY(mpCapGraphBuilder2->FindPin(mpCapSplitFilt, PINDIR_INPUT, NULL, NULL, TRUE, 0, ~pCapSplitIn), "find DV splitter input");
		DS_VERIFY(mpGraphBuilder->Connect(mpRealCapturePin, pCapSplitIn), "connect capture -> dv splitter");

		DS_VERIFY(mpCapGraphBuilder2->FindPin(mpCapSplitFilt, PINDIR_OUTPUT, NULL, &MEDIATYPE_Video, TRUE, 0, ~pCapSplitOutVideo), "find DV splitter video output");
		DS_VERIFY(mpCapGraphBuilder2->FindPin(mpCapSplitFilt, PINDIR_OUTPUT, NULL, &MEDIATYPE_Audio, TRUE, 0, ~pCapSplitOutAudio), "find DV splitter audio output");

		// Treat the outputs of the DV splitter as the "real" audio and video capture pins.
		mpRealCapturePin = pCapSplitOutVideo;
		mpRealAudioPin = pCapSplitOutAudio;
	} else {
		hr = mpCapGraphBuilder2->FindPin(mpCapFilt, PINDIR_OUTPUT, &PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, TRUE, 0, ~mpRealCapturePin);

		DS_VERIFY(hr, "find capture pin");

		interleaved = false;
	}

	// If we have the transform filter, insert it inline now and make its output pin
	// the "capture" pin.
	VDRegistryAppKey key("Hidden features");
	VDStringW name;

	if (key.getString("CapDShow: Transform filter name", name)) {
		IClassFactoryPtr pCF;
		hr = CoGetObject(name.c_str(), NULL, IID_IBaseFilter, (void **)~mpCapTransformFilt);
		if (SUCCEEDED(hr)) {
			IPinPtr pPinTIn, pPinTOut;
			DS_VERIFY(mpGraphBuilder->AddFilter(mpCapTransformFilt, L"Video transform"), "add transform filter");
			DS_VERIFY(mpCapGraphBuilder2->FindPin(mpCapTransformFilt, PINDIR_INPUT, NULL, NULL, TRUE, 0, ~pPinTIn), "find transform filter input");
			DS_VERIFY(mpCapGraphBuilder2->FindPin(mpCapTransformFilt, PINDIR_OUTPUT, NULL, NULL, TRUE, 0, ~pPinTOut), "find transform filter output");
			DS_VERIFY(mpGraphBuilder->Connect(mpRealCapturePin, pPinTIn), "connect capture -> transform");
			mpShadowedRealCapturePin = mpRealCapturePin;
			mpRealCapturePin = pPinTOut;
		}
	}

	// Look for a preview pin.  It's actually likely that we won't get
	// one if someone has a USB webcam, so we have to be prepared for
	// it.
	hr = mpCapGraphBuilder2->FindPin(mpCapFilt, PINDIR_OUTPUT, &PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, TRUE, 0, ~mpRealPreviewPin);
	mbGraphHasPreview = SUCCEEDED(hr);

	// Enumerate video formats from the capture pin.
	IEnumMediaTypesPtr pEnum;
	if (SUCCEEDED(mpRealCapturePin->EnumMediaTypes(~pEnum))) {
		AM_MEDIA_TYPE *pMediaType;
		for(;;) {
			HRESULT hr = pEnum->Next(1, &pMediaType, NULL);

			if (hr == VFW_E_ENUM_OUT_OF_SYNC) {
				mPreferredVideoFormats.clear();
				if (FAILED(pEnum->Reset()))
					break;
				continue;
			}

			if (hr != S_OK)
				break;

			if (pMediaType->majortype == MEDIATYPE_Video && pMediaType->formattype == FORMAT_VideoInfo
				&& pMediaType->cbFormat >= sizeof(VIDEOINFOHEADER)) {

				mPreferredVideoFormats.push_back(*pMediaType);
			}

			RizaDeleteMediaType(pMediaType);
		}
	}

	// Look for an audio pin. We may need to attach a null renderer to
	// it for smooth previews.
	if (!interleaved)
		hr = mpCapGraphBuilder2->FindPin(mpCapFilt, PINDIR_OUTPUT, NULL, &MEDIATYPE_Audio, TRUE, 0, ~mpRealAudioPin);

	// Get video format configurator

	hr = mpCapGraphBuilder2->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Interleaved, mpCapFilt, IID_IAMStreamConfig, (void **)~mpVideoConfigCap);

	if (FAILED(hr))
		hr = mpCapGraphBuilder2->FindInterface(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video, mpCapFilt, IID_IAMStreamConfig, (void **)~mpVideoConfigCap);

	DS_VERIFY(hr, "find video format config if");

	hr = mpCapGraphBuilder2->FindInterface(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Interleaved, mpCapFilt, IID_IAMStreamConfig, (void **)~mpVideoConfigPrv);

	if (FAILED(hr))
		hr = mpCapGraphBuilder2->FindInterface(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video, mpCapFilt, IID_IAMStreamConfig, (void **)~mpVideoConfigPrv);

	// Look for a video port pin. We _HAVE_ to render this if it exists; otherwise,
	// the ATI All-in-Wonder driver can lock on a wait in kernel mode and zombie
	// our process. And no, a Null Renderer doesn't work. It's OK for this to fail.

	hr = mpCapGraphBuilder2->FindPin(mpCapFilt, PINDIR_OUTPUT, &PIN_CATEGORY_VIDEOPORT, NULL, FALSE, 0, ~mpCapFiltVideoPortPin);

	// Check for VFW capture dialogs, TV tuner, and crossbar

	mpCapGraphBuilder2->FindInterface(NULL, NULL, mpCapFilt, IID_IAMVfwCaptureDialogs, (void **)~mpVFWDialogs);
	mpCapGraphBuilder2->FindInterface(NULL, NULL, mpCapFilt, IID_IAMCrossbar, (void **)~mpCrossbar);
	if (mpCrossbar) {
		IBaseFilterPtr pXFilt;

		if (SUCCEEDED(mpCrossbar->QueryInterface(IID_IBaseFilter, (void **)~pXFilt)))
			mpCapGraphBuilder2->FindInterface(&LOOK_UPSTREAM_ONLY, NULL, pXFilt, IID_IAMCrossbar, (void **)~mpCrossbar2);
	}

	// Search for tuner interfaces. Note that IAMTVTuner inherits from IAMTuner.
	if (SUCCEEDED(mpCapGraphBuilder2->FindInterface(NULL, NULL, mpCapFilt, IID_IAMTVTuner, (void **)~mpTVTuner)))
		mpTuner = mpTVTuner;
	else
		mpCapGraphBuilder2->FindInterface(NULL, NULL, mpCapFilt, IID_IAMTuner, (void **)~mpTuner);

	mpCapGraphBuilder2->FindInterface(NULL, NULL, mpCapFilt, IID_IAMAnalogVideoDecoder, (void **)~mpAnalogVideoDecoder);
	mpCapGraphBuilder2->FindInterface(NULL, NULL, mpCapFilt, IID_IAMVideoProcAmp, (void **)~mpVideoProcAmp);

	// If there is at least one crossbar, examine the crossbars to see if we can spot
	// an audio input switch.
	mpAudioCrossbar = NULL;
	mpVideoCrossbar = NULL;
	for(int i=0; i<2; ++i) {
		IAMCrossbar *pCrossbar;
		if (i == 0)
			pCrossbar = mpCrossbar;
		else
			pCrossbar = mpCrossbar2;

		if (!pCrossbar)
			continue;

		long outputs, inputs;
		if (FAILED(pCrossbar->get_PinCounts(&outputs, &inputs)))
			continue;

		for(int pin=0; pin<outputs; ++pin) {
			long related;
			long phystype;

			if (FAILED(pCrossbar->get_CrossbarPinInfo(FALSE, pin, &related, &phystype)))
				continue;

			if (!mpAudioCrossbar) {
				if (phystype == PhysConn_Audio_AudioDecoder) {
					mpAudioCrossbar = pCrossbar;
					mAudioCrossbarOutput = pin;
				}
			}
			
			if (!mpVideoCrossbar) {
				if (phystype == PhysConn_Video_VideoDecoder) {
					mpVideoCrossbar = pCrossbar;
					mVideoCrossbarOutput = pin;
				}
			}
		}
	}

	mCurrentAudioSource = -1;
	mCurrentVideoSource = -1;
	mVideoSources.clear();
	mAudioSources.clear();

	// Scan crossbars for sources
	if (mpAudioCrossbar)
		mCurrentAudioSource = EnumerateCrossbarSources(mAudioSources, mpAudioCrossbar, mAudioCrossbarOutput);

	if (mpVideoCrossbar)
		mCurrentVideoSource = EnumerateCrossbarSources(mVideoSources, mpVideoCrossbar, mVideoCrossbarOutput);

	DS_VERIFY(mpGraphBuilder->QueryInterface(IID_IMediaControl, (void **)~mpGraphControl), "find graph control interface");

	// Enumerate audio drivers. If the capture filter supports audio, make it audio
	// device zero.
	if (mpRealAudioPin) {
		FILTER_INFO fi;

		if (SUCCEEDED(mpCapFilt->QueryFilterInfo(&fi))) {
			mAudioDevices.push_back(tDeviceVector::value_type(NULL, VDStringW(fi.achName)));

			fi.pGraph->Release();
		}
	}

	Enumerate(mAudioDevices, CLSID_AudioInputDeviceCategory);

	// Select the first audio device if there is one.
	mAudioDeviceIndex = -1;
	if (!mAudioDevices.empty())
		SetAudioDevice(0);

	UpdateDisplay();

	return true;
}

void VDCaptureDriverDS::Shutdown() {
	if (mpVideoQualProp) {
		mpVideoQualProp->Release();
		mpVideoQualProp = NULL;
	}

	if (mpVideoWindow) {
		mpVideoWindow->put_Visible(OAFALSE);
		mpVideoWindow->put_Owner(NULL);
		mpVideoWindow = NULL;
	}

	mVideoWindows.clear();

	if (mpGraphBuilder)
		TearDownGraph();

	// FIXME:	We need to tear down the filter graph manager, but to do so we
	//			need to manually kill the smart pointers.  This is not the
	//			cleanest way to wipe a graph and there's probably something
	//			wrong with doing it this way.

	mpAudioCapFilt		= NULL;
	mpAudioGrabber		= NULL;
	mpAudioConfig		= NULL;

	mpCapFilt			= NULL;
	mpCapSplitFilt		= NULL;
	mpCapTransformFilt	= NULL;
	mpShadowedRealCapturePin	= NULL;
	mpRealCapturePin	= NULL;
	mpRealPreviewPin	= NULL;
	mpCapFiltVideoPortPin	= NULL;
	mpRealAudioPin		= NULL;
	mpAudioPin			= NULL;
	mpAnalogVideoDecoder = NULL;
	mpCrossbar			= NULL;
	mpCrossbar2			= NULL;
	mpTuner				= NULL;
	mpTVTuner			= NULL;
	mpVideoProcAmp		= NULL;
	mpVideoGrabber		= NULL;
	mpVFWDialogs		= NULL;
	mpVideoConfigCap	= NULL;
	mpVideoConfigPrv	= NULL;
	mpMediaEventEx		= NULL;
	mpGraphBuilder				= NULL;
	mpCapGraphBuilder2		= NULL;
	mpGraphControl		= NULL;

	mpAudioCrossbar		= NULL;
	mpVideoCrossbar		= NULL;

	if (mhwndEventSink) {
		DestroyWindow(mhwndEventSink);
		mhwndEventSink = NULL;
	}

	mAudioDevices.clear();
	mAudioInputs.clear();

	if (mdwRegisterGraph) {
		RemoveFromRot(mdwRegisterGraph);
		mdwRegisterGraph = 0;
	}

	if (mCaptureThread) {
		CloseHandle(mCaptureThread);
		mCaptureThread = NULL;
	}

	if (MyError *e = mpCaptureError.xchg(NULL))
		delete e;
}

void VDCaptureDriverDS::LockUpdates() {
	++mUpdateLocks;
}

void VDCaptureDriverDS::UnlockUpdates() {
	--mUpdateLocks;
	VDASSERT((int)mUpdateLocks >= 0);

	if (!mUpdateLocks) {
		if (mbUpdatePending)
			UpdateDisplay();
		else if (mbStartPending)
			StartGraph();

		mbUpdatePending = mbStartPending = false;
	}
}

bool VDCaptureDriverDS::IsHardwareDisplayAvailable() {
	return true;
}

void VDCaptureDriverDS::SetDisplayMode(nsVDCapture::DisplayMode mode) {
	if (mDisplayMode == mode)
		return;

	mDisplayMode = mode;

	if (mode == kDisplayNone) {
		TearDownGraph();
		return;
	}

	UpdateDisplay();
}

nsVDCapture::DisplayMode VDCaptureDriverDS::GetDisplayMode() {
	return mDisplayMode;
}

void VDCaptureDriverDS::SetDisplayRect(const vdrect32& r) {
	mDisplayRect = r;

	if (mpVideoWindow) {
		mpVideoWindow->put_Left(r.left);
		mpVideoWindow->put_Top(r.top);
		mpVideoWindow->put_Width(r.width());
		mpVideoWindow->put_Height(r.height());
	}
}

vdrect32 VDCaptureDriverDS::GetDisplayRectAbsolute() {
	return mDisplayRect;
}

void VDCaptureDriverDS::SetDisplayVisibility(bool vis) {
	if (mbDisplayVisible == vis)
		return;

	mbDisplayVisible = vis;

	if (mpVideoWindow)
		mpVideoWindow->put_Visible(vis ? OATRUE : OAFALSE);
}

void VDCaptureDriverDS::SetFramePeriod(sint32 framePeriod100nsUnits) {
	AM_MEDIA_TYPE *past;
	VDFraction pf;
	bool bRet = false;

	StopGraph();

	if (mpVideoConfigCap && SUCCEEDED(mpVideoConfigCap->GetFormat(&past))) {
		if (past->formattype == FORMAT_VideoInfo) {
			VIDEOINFOHEADER *pvih = (VIDEOINFOHEADER *)past->pbFormat;

			pvih->AvgTimePerFrame = framePeriod100nsUnits;

			bRet = SUCCEEDED(mpVideoConfigCap->SetFormat(past));
			VDASSERT(bRet);
		}

		RizaDeleteMediaType(past);
	}

	if (mpVideoConfigPrv && SUCCEEDED(mpVideoConfigPrv->GetFormat(&past))) {
		if (past->formattype == FORMAT_VideoInfo) {
			VIDEOINFOHEADER *pvih = (VIDEOINFOHEADER *)past->pbFormat;

			pvih->AvgTimePerFrame = framePeriod100nsUnits;

			bRet = SUCCEEDED(mpVideoConfigPrv->SetFormat(past));
			VDASSERT(bRet);
		}

		RizaDeleteMediaType(past);
	}

	CheckForChanges();

	VDDEBUG("Desired frame period = %u*100ns, actual = %u*100ns\n", framePeriod100nsUnits, GetFramePeriod());

	StartGraph();

	VDASSERT(bRet);
}

sint32 VDCaptureDriverDS::GetFramePeriod() {
	AM_MEDIA_TYPE *past;
	sint32 rate;
	bool bRet = false;

	if (SUCCEEDED(mpVideoConfigCap->GetFormat(&past))) {
		if (past->formattype == FORMAT_VideoInfo || past->formattype == FORMAT_MPEGVideo) {
			const VIDEOINFOHEADER *pvih = (const VIDEOINFOHEADER *)past->pbFormat;

			rate = VDClampToSint32(pvih->AvgTimePerFrame);

			bRet = true;
		} else if (past->formattype == FORMAT_VideoInfo2 || past->formattype == FORMAT_MPEG2Video) {
			const VIDEOINFOHEADER2 *pvih = (const VIDEOINFOHEADER2 *)past->pbFormat;

			rate = VDClampToSint32(pvih->AvgTimePerFrame);

			bRet = true;
		} else if (past->formattype == FORMAT_DvInfo) {
			const DVINFO& dvi = *(const DVINFO *)past->pbFormat;

			if (dvi.dwDVVAuxSrc & 0x200000)
				rate = 400000;	// PAL
			else
				rate = 333667;	// NTSC

			bRet = true;
		}

		RizaDeleteMediaType(past);
	}

	if (!bRet) {
		VDASSERT(false);
		return 10000000/15;
	}

	return rate;
}

uint32 VDCaptureDriverDS::GetPreviewFrameCount() {
	int framesDrawn;

	if (mDisplayMode == kDisplayAnalyze)
		return mVideoCallback.GetFrameCount();
	else if (mpVideoQualProp && SUCCEEDED(mpVideoQualProp->get_FramesDrawn(&framesDrawn)))
		return (uint32)framesDrawn;

	return 0;
}

bool VDCaptureDriverDS::GetVideoFormat(vdstructex<BITMAPINFOHEADER>& vformat) {
	AM_MEDIA_TYPE *pmt = NULL;
	// If the DV splitter is in use, we need to query what its video pin outputs and
	// not the capture filter itself.
	if (mpCapSplitFilt || mpCapTransformFilt) {
		IEnumMediaTypesPtr pEnum;

		if (SUCCEEDED(mpRealCapturePin->EnumMediaTypes(~pEnum))) {
			ULONG cFetched;

			if (S_OK == pEnum->Next(1, &pmt, &cFetched)) {
				if (pmt->majortype == MEDIATYPE_Video
					&& pmt->formattype == FORMAT_VideoInfo) {
					vformat.assign(&((VIDEOINFOHEADER *)pmt->pbFormat)->bmiHeader, pmt->cbFormat - offsetof(VIDEOINFOHEADER, bmiHeader));
					RizaDeleteMediaType(pmt);
					return true;
				}

				RizaDeleteMediaType(pmt);
			}
		}
	} else if (SUCCEEDED(mpVideoConfigCap->GetFormat(&pmt))) {
		if (pmt->majortype == MEDIATYPE_Video
			&& pmt->formattype == FORMAT_VideoInfo) {
			vformat.assign(&((VIDEOINFOHEADER *)pmt->pbFormat)->bmiHeader, pmt->cbFormat - offsetof(VIDEOINFOHEADER, bmiHeader));
			RizaDeleteMediaType(pmt);
			return true;
		}
		RizaDeleteMediaType(pmt);
	}

	vformat.clear();
	return false;
}

bool VDCaptureDriverDS::SetVideoFormat(const BITMAPINFOHEADER *pbih, uint32 size) {
	vdstructex<VIDEOINFOHEADER> vhdr;

	vhdr.resize(offsetof(VIDEOINFOHEADER, bmiHeader) + size);
	memcpy(&vhdr->bmiHeader, pbih, size);

	vhdr->rcSource.left		= 0;
	vhdr->rcSource.top		= 0;
	vhdr->rcSource.right	= 0;
	vhdr->rcSource.bottom	= 0;
	vhdr->rcTarget			= vhdr->rcSource;
	vhdr->AvgTimePerFrame	= GetFramePeriod();
	vhdr->dwBitRate			= VDRoundToInt(pbih->biSizeImage * 80000000.0 / vhdr->AvgTimePerFrame);
	vhdr->dwBitErrorRate	= 0;

	AM_MEDIA_TYPE vformat;

	vformat.majortype			= MEDIATYPE_Video;
	vformat.subtype.Data1		= pbih->biCompression;
	vformat.subtype.Data2		= 0;
	vformat.subtype.Data3		= 0x0010;
	vformat.subtype.Data4[0]	= 0x80;
	vformat.subtype.Data4[1]	= 0x00;
	vformat.subtype.Data4[2]	= 0x00;
	vformat.subtype.Data4[3]	= 0xAA;
	vformat.subtype.Data4[4]	= 0x00;
	vformat.subtype.Data4[5]	= 0x38;
	vformat.subtype.Data4[6]	= 0x9B;
	vformat.subtype.Data4[7]	= 0x71;
	vformat.bFixedSizeSamples	= FALSE;
	vformat.bTemporalCompression = TRUE;
	vformat.lSampleSize			= 0;
	vformat.formattype			= FORMAT_VideoInfo;
	vformat.pUnk				= NULL;
	vformat.cbFormat			= vhdr.size();
	vformat.pbFormat			= (BYTE *)vhdr.data();

	// Check for one of the special subtypes....

	bool fixed = false;

	switch(pbih->biCompression) {
	case BI_RGB:
		if (pbih->biPlanes == 1) {
			if (pbih->biBitCount == 1) {
				vformat.subtype = MEDIASUBTYPE_RGB1;
				fixed = true;
			} else if (pbih->biBitCount == 4) {
				vformat.subtype = MEDIASUBTYPE_RGB4;
				fixed = true;
			} else if (pbih->biBitCount == 8) {
				vformat.subtype = MEDIASUBTYPE_RGB8;
				fixed = true;
			} else if (pbih->biBitCount == 16) {
				vformat.subtype = MEDIASUBTYPE_RGB555;
				fixed = true;
			} else if (pbih->biBitCount == 24) {
				vformat.subtype = MEDIASUBTYPE_RGB24;
				fixed = true;
			} else if (pbih->biBitCount == 32) {
				vformat.subtype = MEDIASUBTYPE_RGB32;
				fixed = true;
			}
		}
		break;
	case BI_BITFIELDS:
		{
			const BITMAPV4HEADER& v4hdr = *(const BITMAPV4HEADER *)pbih;
			DWORD a = 0;

			if (v4hdr.bV4Size >= sizeof(BITMAPV4HEADER))
				a = v4hdr.bV4AlphaMask;

			const DWORD r = v4hdr.bV4RedMask;
			const DWORD g = v4hdr.bV4GreenMask;
			const DWORD b = v4hdr.bV4BlueMask;
			const int bits = v4hdr.bV4BitCount;

			switch(bits) {
			case 16:
				if (r == 0x7c00 && g == 0x03e0 && b == 0x001f) {
					if (!a) {
						vformat.subtype = MEDIASUBTYPE_RGB555;
						fixed = true;
					} else if (a == 0x8000) {
						vformat.subtype = MEDIASUBTYPE_ARGB1555;
						fixed = true;
					}
				} else if (r == 0xf800 && g == 0x07e0 && b == 0x001f) {
					if (!a)	{
						vformat.subtype = MEDIASUBTYPE_RGB565;
						fixed = true;
					}
				}
				break;
			case 24:
				if (!a && r == 0xff0000 && g == 0xff00 && b == 0xff) {
					vformat.subtype = MEDIASUBTYPE_RGB24;
					fixed = true;
				}
				break;
			case 32:
				if (r == 0xff0000 && g == 0xff00 && b == 0xff) {
					if (!a) {
						vformat.subtype = MEDIASUBTYPE_RGB32;
						fixed = true;
					} else if (a == 0xff000000) {
						vformat.subtype = MEDIASUBTYPE_ARGB32;
						fixed = true;
					}
				}
				break;
			}
		}
		break;
	case MAKEFOURCC('A', 'Y', 'U', 'V'):
	case MAKEFOURCC('U', 'Y', 'V', 'Y'):
	case MAKEFOURCC('Y', '4', '1', '1'):
	case MAKEFOURCC('Y', '4', '1', 'P'):
	case MAKEFOURCC('Y', '2', '1', '1'):
	case MAKEFOURCC('Y', 'U', 'Y', '2'):
	case MAKEFOURCC('Y', 'V', 'Y', 'U'):
	case MAKEFOURCC('Y', 'U', 'Y', 'V'):
	case MAKEFOURCC('I', 'F', '0', '9'):
	case MAKEFOURCC('I', 'Y', 'U', 'V'):
	case MAKEFOURCC('Y', 'V', '1', '6'):
	case MAKEFOURCC('Y', 'V', '1', '2'):
	case MAKEFOURCC('Y', 'V', 'U', '9'):
		// The FOURCC encoding is already correct for these formats, but
		// we also happen to know that these are uncompressed formats.
		fixed = true;
		break;
	}

	if (fixed) {
		vformat.bFixedSizeSamples		= TRUE;
		vformat.bTemporalCompression	= FALSE;
		vformat.lSampleSize				= pbih->biSizeImage;
	} else {
		// We don't recognize the format, so iterate through the list of
		// preferred formats and see if one of the existing formats is
		// similar to what we have here. If so, copy the fields from
		// that format.

		VideoFormats::const_iterator it(mPreferredVideoFormats.begin()), itEnd(mPreferredVideoFormats.end());

		for(; it!=itEnd; ++it) {
			const AM_MEDIA_TYPE& amtype = *it;
			const VIDEOINFOHEADER& vhdr2 = *(const VIDEOINFOHEADER *)amtype.pbFormat;

			if (vhdr2.bmiHeader.biCompression == pbih->biCompression) {
				vformat.bFixedSizeSamples		= amtype.bFixedSizeSamples;
				vformat.bTemporalCompression	= amtype.bTemporalCompression;
				vformat.lSampleSize				= amtype.lSampleSize;

				// tweak the frame rate to be the same if they're within the margin
				// of error (since we use 1us and not 100ns units)
				__int64 deltaTime = vhdr->AvgTimePerFrame - vhdr2.AvgTimePerFrame;
				if (-20 < deltaTime && deltaTime < 20)
					vhdr->AvgTimePerFrame = vhdr2.AvgTimePerFrame;

				break;
			}
		}
	}

	// tear down graph
	TearDownGraph();

	// check if format is compatible
	bool success = false;
	HRESULT hr = mpVideoConfigCap->SetFormat(&vformat);
	if (SUCCEEDED(hr)) {
		success = true;
	} else {
		VDDEBUG("Riza/CapDShow: Unable to set video format: [%s]\n", GetDXErrorName(hr));
	}

	// rebuild display
	UpdateDisplay();

	return success;
}

bool VDCaptureDriverDS::SetTunerChannel(int channel) {
	return mpTuner && SUCCEEDED(mpTuner->put_Channel(channel, AMTUNER_SUBCHAN_DEFAULT, AMTUNER_SUBCHAN_DEFAULT));
}

int VDCaptureDriverDS::GetTunerChannel() {
	long ch, videoSub, audioSub;
	if (mpTuner && SUCCEEDED(mpTuner->get_Channel(&ch, &videoSub, &audioSub))) {
		return ch;
	}

	return -1;
}

bool VDCaptureDriverDS::GetTunerChannelRange(int& minChannel, int& maxChannel) {
	long mn, mx;

	if (mpTuner && SUCCEEDED(mpTuner->ChannelMinMax(&mn, &mx))) {
		minChannel = mn;
		maxChannel = mx;
		return true;
	}

	return false;
}

namespace
{
	struct TunerInfo {
		bool Init(IUnknown *pTuner) {
			if (!pTuner)
				return false;

			if (FAILED(pTuner->QueryInterface(IID_IKsPropertySet, (void **)~mpPropSet)))
				return false;

			DWORD dwSupport;
			if (FAILED(mpPropSet->QuerySupported(PROPSETID_TUNER, KSPROPERTY_TUNER_MODE_CAPS, &dwSupport)))
				return false;

			if (!(dwSupport & KSPROPERTY_SUPPORT_GET))
				return false;

			DWORD actual;

			// uselessness of IKsPropertySet::Get() docs is astounding... InstanceData is supposed
			// to be the portion of the struct after sizeof(KSPROPERTY).
			memset(&mTunerModeCaps, 0, sizeof mTunerModeCaps);
			mTunerModeCaps.Mode = KSPROPERTY_TUNER_MODE_TV;
			if (FAILED(mpPropSet->Get(PROPSETID_TUNER, KSPROPERTY_TUNER_MODE_CAPS, (KSPROPERTY *)&mTunerModeCaps + 1, sizeof mTunerModeCaps - sizeof(KSPROPERTY), &mTunerModeCaps, sizeof mTunerModeCaps, &actual)))
				return false;

			return true;
		}

		IKsPropertySetPtr mpPropSet;
		KSPROPERTY_TUNER_MODE_CAPS_S mTunerModeCaps;
	};
}

uint32 VDCaptureDriverDS::GetTunerFrequencyPrecision() {
	TunerInfo info;

	if (!info.Init(mpTuner))
		return 0;

	return info.mTunerModeCaps.TuningGranularity;
}

uint32 VDCaptureDriverDS::GetTunerExactFrequency() {
	TunerInfo info;

	if (!info.Init(mpTuner))
		return 0;

	KSPROPERTY_TUNER_FREQUENCY_S freq = {0};
	DWORD actual;

	if (FAILED(info.mpPropSet->Get(PROPSETID_TUNER, KSPROPERTY_TUNER_FREQUENCY, (KSPROPERTY *)&freq + 1, sizeof freq - sizeof(KSPROPERTY), &freq, sizeof freq, &actual)))
		return 0;

	return freq.Frequency;
}

bool VDCaptureDriverDS::SetTunerExactFrequency(uint32 freq) {
	TunerInfo info;

	if (!info.Init(mpTuner))
		return 0;

	if (freq < info.mTunerModeCaps.MinFrequency || freq > info.mTunerModeCaps.MaxFrequency)
		return 0;

	KSPROPERTY_TUNER_FREQUENCY_S freqData = {0};
	DWORD actual;

	if (FAILED(info.mpPropSet->Get(PROPSETID_TUNER, KSPROPERTY_TUNER_FREQUENCY, (KSPROPERTY *)&freq + 1, sizeof freqData - sizeof(KSPROPERTY), &freqData, sizeof freqData, &actual)))
		return 0;

	freqData.Frequency = freq;
	freqData.TuningFlags = KS_TUNER_TUNING_EXACT;

	HRESULT hr = info.mpPropSet->Set(PROPSETID_TUNER, KSPROPERTY_TUNER_FREQUENCY, (KSPROPERTY *)&freqData + 1, sizeof freqData - sizeof(KSPROPERTY), &freqData, sizeof freqData);

	return SUCCEEDED(hr);
}

nsVDCapture::TunerInputMode	VDCaptureDriverDS::GetTunerInputMode() {
	if (!mpTVTuner)
		return kTunerInputUnknown;

	long index;
	HRESULT hr = mpTVTuner->get_ConnectInput(&index);
	if (FAILED(hr))
		return kTunerInputUnknown;

	TunerInputType type;
	hr = mpTVTuner->get_InputType(index, &type);
	if (FAILED(hr))
		return kTunerInputUnknown;

	switch(type) {
		case TunerInputCable:
			return kTunerInputCable;
		case TunerInputAntenna:
			return kTunerInputAntenna;
		default:
			return kTunerInputUnknown;
	}
}

void VDCaptureDriverDS::SetTunerInputMode(nsVDCapture::TunerInputMode tunerMode) {
	if (!mpTVTuner)
		return;

	long index;
	HRESULT hr = mpTVTuner->get_ConnectInput(&index);
	if (FAILED(hr))
		return;

	TunerInputType type;

	switch(tunerMode) {
		case kTunerInputAntenna:
		default:
			type = TunerInputAntenna;
			break;

		case kTunerInputCable:
			type = TunerInputCable;
			break;
	}

	mpTVTuner->put_InputType(index, type);
}

int VDCaptureDriverDS::GetAudioDeviceCount() {
	return mAudioDevices.size();
}

const wchar_t *VDCaptureDriverDS::GetAudioDeviceName(int idx) {
	if ((unsigned)idx >= mAudioDevices.size())
		return NULL;

	return mAudioDevices[idx].second.c_str();
}

bool VDCaptureDriverDS::SetAudioDevice(int idx) {
	if (idx == mAudioDeviceIndex)
		return true;

	if ((unsigned)idx >= mAudioDevices.size())
		idx = -1;

	StopGraph();

	// Kill an existing device.
	if (mpAudioCapFilt) {
		DestroySubgraph(mpGraphBuilder, mpAudioCapFilt, NULL, NULL);
		VDVERIFY(SUCCEEDED(mpGraphBuilder->RemoveFilter(mpAudioCapFilt)));
		mpAudioCapFilt = NULL;
	}

	mAudioDeviceIndex = -1;
	mCurrentAudioInput = -1;
	mAudioInputs.clear();

	// Attempt to connect to existing device

	bool success = false;

	if (idx < 0) {
		success = true;
	} else {
		const IMonikerPtr& pAudioDeviceMoniker = mAudioDevices[idx].first;
		HRESULT hr = S_OK;

		if (!pAudioDeviceMoniker) {
			VDASSERT(mpRealAudioPin);
			mpAudioPin = mpRealAudioPin;
		} else {
			hr = pAudioDeviceMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void **)~mpAudioCapFilt);
			if (FAILED(hr))
				VDDEBUG("Failed to create audio filter: %s\n", GetDXErrorName(hr));

			// Do NOT add the audio capture filter into the graph at this
			// point. See BuildGraph() for an explanation of why.

			if (SUCCEEDED(hr)) {
				hr = mpCapGraphBuilder2->FindPin(mpAudioCapFilt, PINDIR_OUTPUT, NULL, &MEDIATYPE_Audio, TRUE, 0, ~mpAudioPin);

				if (FAILED(hr))
					VDDEBUG("Couldn't find audio output pin: %s\n", GetDXErrorName(hr));
			}

			// scan the inputs of the audio capture filter and look for IAMAudioInputMixer
			// interfaces

			IEnumPinsPtr pEnumPins;
			if (SUCCEEDED(mpAudioCapFilt->EnumPins(~pEnumPins))) {
				IPinPtr pPin;

				mCurrentAudioInput = -1;

				for(;;) {
					ULONG actual;
					HRESULT hr = pEnumPins->Next(1, ~pPin, &actual);

					if (hr == VFW_E_ENUM_OUT_OF_SYNC) {
						mAudioInputs.clear();
						mCurrentAudioInput = -1;
						if (FAILED(pEnumPins->Reset()))
							break;
						continue;
					}

					if (hr != S_OK)
						break;

					PIN_DIRECTION dir;
					if (SUCCEEDED(pPin->QueryDirection(&dir)) && dir == PINDIR_INPUT) {
						IAMAudioInputMixerPtr pInputMixer;

						if (SUCCEEDED(pPin->QueryInterface(IID_IAMAudioInputMixer, (void **)~pInputMixer))) {
							// attempt to get pin name
							PIN_INFO pinInfo;
							if (SUCCEEDED(pPin->QueryPinInfo(&pinInfo))) {
								mAudioInputs.push_back(AudioInput(pInputMixer, VDStringW(pinInfo.achName)));

								// if we haven't already found an enabled pin, check if this one
								// is enabled
								if (mCurrentAudioInput < 0) {
									BOOL enabled;
									if (SUCCEEDED(pInputMixer->get_Enable(&enabled)) && enabled)
										mCurrentAudioInput = mAudioInputs.size() - 1;
								}

								pinInfo.pFilter->Release();
							}
						}
					}
				}
			}
		}

		mAudioDeviceIndex = idx;

		// Reset audio format to first available format.
		if (mpAudioPin) {
			std::list<vdstructex<WAVEFORMATEX> > aformats;

			GetAvailableAudioFormats(aformats);

			mAudioFormat.clear();
			if (!aformats.empty())
				mAudioFormat = aformats.front();
		}
	}

	UpdateDisplay();

	return success;
}

int VDCaptureDriverDS::GetAudioDeviceIndex() {
	return mAudioDeviceIndex;
}

bool VDCaptureDriverDS::IsAudioDeviceIntegrated(int idx) {
	return (unsigned)idx < mAudioDevices.size() && !mAudioDevices[idx].first;
}

int VDCaptureDriverDS::GetVideoSourceCount() {
	return mVideoSources.size();
}

const wchar_t *VDCaptureDriverDS::GetVideoSourceName(int idx) {
	return (unsigned)idx < mVideoSources.size() ? mVideoSources[idx].mName.c_str() : NULL;
}

bool VDCaptureDriverDS::SetVideoSource(int idx) {
	if (!mpVideoCrossbar)
		return false;

	const unsigned nSources = mVideoSources.size();
	if ((unsigned)idx >= nSources && idx != -1)
		return false;

	if (idx == mCurrentVideoSource)
		return true;

	const int inputPin = idx<0 ? -1 : mVideoSources[idx].mCrossbarPin;

	VDASSERT(inputPin >= -1);
	if (FAILED(mpVideoCrossbar->Route(mVideoCrossbarOutput, inputPin)))
		return false;

	mCurrentVideoSource = idx;

	return true;
}

int VDCaptureDriverDS::GetVideoSourceIndex() {
	return mCurrentVideoSource;
}

int VDCaptureDriverDS::GetAudioSourceCount() {
	return mAudioSources.size();
}

const wchar_t *VDCaptureDriverDS::GetAudioSourceName(int idx) {
	return (unsigned)idx < mAudioSources.size() ? mAudioSources[idx].mName.c_str() : NULL;
}

bool VDCaptureDriverDS::SetAudioSource(int idx) {
	if (!mpAudioCrossbar)
		return false;

	const unsigned nSources = mAudioSources.size();
	if ((unsigned)idx >= nSources && idx != -1)
		return false;

	if (idx == mCurrentAudioSource)
		return true;

	const int inputPin = idx<0 ? -1 : mAudioSources[idx].mCrossbarPin;

	VDASSERT(inputPin >= -1);
	if (FAILED(mpAudioCrossbar->Route(mAudioCrossbarOutput, inputPin)))
		return false;

	mCurrentAudioSource = idx;

	return true;
}

int VDCaptureDriverDS::GetAudioSourceIndex() {
	return mCurrentAudioSource;
}

int VDCaptureDriverDS::GetAudioSourceForVideoSource(int idx) {
	if (idx == -1)
		return -1;

	if ((unsigned)idx >= mVideoSources.size())
		return -2;

	int audioPhysType = -1;

	switch(mVideoSources[idx].mPhysicalType) {
	case PhysConn_Video_Tuner:		audioPhysType = PhysConn_Audio_Tuner; break;
	case PhysConn_Video_Composite:	audioPhysType = PhysConn_Audio_Line; break;
	case PhysConn_Video_SVideo:		audioPhysType = PhysConn_Audio_Line; break;
	case PhysConn_Video_SCSI:		audioPhysType = PhysConn_Audio_SCSI; break;
	case PhysConn_Video_AUX:		audioPhysType = PhysConn_Audio_AUX; break;
	case PhysConn_Video_1394:		audioPhysType = PhysConn_Audio_1394; break;
	case PhysConn_Video_USB:		audioPhysType = PhysConn_Audio_USB; break;
	default:						return -2;
	}

	const int audioSources = mAudioSources.size();

	for(int i=0; i<audioSources; ++i) {
		if (mAudioSources[i].mPhysicalType == audioPhysType)
			return i;
	}

	return -2;
}

int VDCaptureDriverDS::GetAudioInputCount() {
	return mpAudioPin != 0 ? mAudioInputs.size() : 0;
}

const wchar_t *VDCaptureDriverDS::GetAudioInputName(int idx) {
	return (unsigned)idx < mAudioInputs.size() ? mAudioInputs[idx].mName.c_str() : NULL;
}

bool VDCaptureDriverDS::SetAudioInput(int idx) {
	if (!mpAudioPin)
		return false;

	const size_t nInputs = mAudioInputs.size();
	if ((unsigned)idx >= nInputs && idx != -1)
		return false;

	if (idx == mCurrentAudioInput)
		return true;

	// run through all of the pins and set the enables accordingly
	for(int i=0; (unsigned)i<nInputs; ++i) {
		const AudioInput& ai = mAudioInputs[i];

		if (ai.mMixerEnable) {
			// We deliberately ignore the return value here. The reason is
			// that the put_Enable method can return E_NOTIMPL when
			// attempting to set an input to FALSE on a device that can only
			// support one enabled input at a time (Creative SBLive!). This
			// strikes me as BS since E_NOTIMPL is supposed to mean a function
			// that is not implemented, but we must handle it nevertheless.
			ai.mMixerEnable->put_Enable(i == idx);
		}
	}

	mCurrentAudioInput = idx;

	return true;
}

int VDCaptureDriverDS::GetAudioInputIndex() {
	return mpAudioPin != 0 ? mCurrentAudioInput : -1;
}

bool VDCaptureDriverDS::IsAudioCapturePossible() {
	return !!mpAudioPin;
}

bool VDCaptureDriverDS::IsAudioCaptureEnabled() {
	return mbAudioCaptureEnabled && !!mpAudioPin;
}

bool VDCaptureDriverDS::IsAudioPlaybackPossible() {
	return !!mpAudioPin;
}

bool VDCaptureDriverDS::IsAudioPlaybackEnabled() {
	return mpAudioPin && mbAudioPlaybackEnabled;
}

void VDCaptureDriverDS::SetAudioCaptureEnabled(bool b) {
	if (mbAudioCaptureEnabled == b)
		return;

	mbAudioCaptureEnabled = b;

	if (mbAudioAnalysisEnabled)
		UpdateDisplay();
}

void VDCaptureDriverDS::SetAudioAnalysisEnabled(bool b) {
	if (mbAudioAnalysisEnabled == b)
		return;

	mbAudioAnalysisEnabled = b;

	if (mbAudioCaptureEnabled)
		UpdateDisplay();
}

void VDCaptureDriverDS::SetAudioPlaybackEnabled(bool b) {
	if (mbAudioPlaybackEnabled == b)
		return;

	mbAudioPlaybackEnabled = b;

	if (mpAudioPin)
		UpdateDisplay();
}

namespace {
	struct AudioFormatSorter {
		bool operator()(const vdstructex<WAVEFORMATEX>& af1, const vdstructex<WAVEFORMATEX>& af2) {
			const WAVEFORMATEX& f1 = *af1;
			const WAVEFORMATEX& f2 = *af2;

			if (f1.wFormatTag < f2.wFormatTag)
				return true;
			if (f1.wFormatTag > f2.wFormatTag)
				return false;

			if (f1.nSamplesPerSec > f2.nSamplesPerSec)
				return true;
			if (f1.nSamplesPerSec < f2.nSamplesPerSec)
				return false;

			if (f1.wBitsPerSample > f2.wBitsPerSample)
				return true;
			if (f1.wBitsPerSample < f2.wBitsPerSample)
				return false;

			if (f1.nChannels > f2.nChannels)
				return true;

			return false;
		}
	};
}

void VDCaptureDriverDS::GetAvailableAudioFormats(std::list<vdstructex<WAVEFORMATEX> >& aformats) {
	aformats.clear();

	if (!mpAudioPin)
		return;

	IEnumMediaTypesPtr pEnum;

	if (FAILED(mpAudioPin->EnumMediaTypes(~pEnum)))
		return;

	AM_MEDIA_TYPE *pMediaType;
	while(S_OK == pEnum->Next(1, &pMediaType, NULL)) {

		if (pMediaType->majortype == MEDIATYPE_Audio && pMediaType->formattype == FORMAT_WaveFormatEx
			&& pMediaType->cbFormat >= sizeof(WAVEFORMATEX)) {
			const WAVEFORMATEX *pwfex = (const WAVEFORMATEX *)pMediaType->pbFormat;

			if (pwfex->wFormatTag == WAVE_FORMAT_PCM)
				aformats.push_back(vdstructex<WAVEFORMATEX>(pwfex, sizeof(WAVEFORMATEX)));
			else if (pwfex->cbSize + sizeof(WAVEFORMATEX) <= pMediaType->cbFormat)
				aformats.push_back(vdstructex<WAVEFORMATEX>(pwfex, pwfex->cbSize + sizeof(WAVEFORMATEX)));
		}

		RizaDeleteMediaType(pMediaType);
	}

	aformats.sort(AudioFormatSorter());

	std::list<vdstructex<WAVEFORMATEX> >::iterator it(aformats.begin()), itEnd(aformats.end());
	if (it != itEnd) {
		for(;;) {
			std::list<vdstructex<WAVEFORMATEX> >::iterator itNext(it);
			++itNext;

			if (itNext == itEnd)
				break;

			const vdstructex<WAVEFORMATEX>& f1 = *it;
			const vdstructex<WAVEFORMATEX>& f2 = *itNext;

			if (f1 == f2)
				aformats.erase(it);

			it = itNext;
		}
	}
}

bool VDCaptureDriverDS::GetAudioFormat(vdstructex<WAVEFORMATEX>& aformat) {
	if (!mpAudioPin)
		return false;

	if (!mAudioFormat.empty()) {
		aformat = mAudioFormat;
		return true;
	}

	return false;
}

bool VDCaptureDriverDS::SetAudioFormat(const WAVEFORMATEX *pwfex, uint32 size) {
	if (!mpAudioPin)
		return false;

	VDWaveFormatAsDShowMediaType	amt(pwfex, size);

	if (SUCCEEDED(mpAudioPin->QueryAccept(&amt))) {
		mAudioFormat.assign(pwfex, size);

		if (mbAudioCaptureEnabled && mbAudioAnalysisEnabled)
			UpdateDisplay();

		return true;
	}

	return false;
}

bool VDCaptureDriverDS::IsDriverDialogSupported(nsVDCapture::DriverDialog dlg) {
	switch(dlg) {
	case kDialogVideoFormat:		return mpVFWDialogs && SUCCEEDED(mpVFWDialogs->HasDialog(VfwCaptureDialog_Format));
	case kDialogVideoSource:		return mpVFWDialogs && SUCCEEDED(mpVFWDialogs->HasDialog(VfwCaptureDialog_Source));
	case kDialogVideoDisplay:
		if (mpCapTransformFilt)
			return DisplayPropertyPages(mpCapTransformFilt, NULL, NULL, 0);
		else
			return mpVFWDialogs && SUCCEEDED(mpVFWDialogs->HasDialog(VfwCaptureDialog_Display));
	case kDialogVideoCaptureFilter:	return DisplayPropertyPages(mpCapFilt, NULL, NULL, 0);
	case kDialogVideoCapturePin:	return DisplayPropertyPages(mpRealCapturePin, NULL, NULL, 0);
	case kDialogVideoPreviewPin:	return DisplayPropertyPages(mpRealPreviewPin, NULL, NULL, 0);
	case kDialogVideoCrossbar:		return DisplayPropertyPages(mpCrossbar, NULL, NULL, 0);
	case kDialogVideoCrossbar2:		return DisplayPropertyPages(mpCrossbar2, NULL, NULL, 0);
	case kDialogTVTuner:			return DisplayPropertyPages(mpTVTuner, NULL, NULL, 0);
	}

	return false;
}

void VDCaptureDriverDS::DisplayDriverDialog(nsVDCapture::DriverDialog dlg) {
	bool bRestart = mbGraphActive;

	switch(dlg) {
	case kDialogVideoDisplay:
		if (mpCapTransformFilt) {
			DisplayPropertyPages(mpCapTransformFilt, mhwndParent, NULL, 0);
			break;
		}
		// fall through
	case kDialogVideoFormat:
	case kDialogVideoSource:

		if (mpVFWDialogs) {						
			// Disable the parent window manually to prevent reentrancy. The
			// dialogs are supposed to do this, but for some reason the VFW dialogs
			// sometimes don't.

			bool wasEnabled = false;
			
			if (mhwndParent) {
				wasEnabled = !!IsWindowEnabled(mhwndParent);

				if (wasEnabled)
					EnableWindow(mhwndParent, FALSE);
			}

			// The filter graph must be stopped for VFW dialogs.
			StopGraph();

			HRESULT hr;
			switch(dlg) {
			case kDialogVideoFormat:
				hr = mpVFWDialogs->ShowDialog(VfwCaptureDialog_Format, mhwndParent);
				break;
			case kDialogVideoSource:
				hr = mpVFWDialogs->ShowDialog(VfwCaptureDialog_Source, mhwndParent);
				break;
			case kDialogVideoDisplay:
				hr = mpVFWDialogs->ShowDialog(VfwCaptureDialog_Display, mhwndParent);
				break;
			}

			// The VFW dialogs have a habit of coming up with a NULL parent handle
			// when we're not in Hardware display mode. I don't know why; it appears
			// to be something funky in the o100vc driver, not in DirectShow. Anyway,
			// we kludge that here.
			SetForegroundWindow(mhwndParent);

			if (hr == VFW_E_CANNOT_CONNECT)
				BuildPreviewGraph();

			// Restart the filter graph.

			if (bRestart)
				StartGraph();

			// reenable main window
			if (mhwndParent && wasEnabled)
				EnableWindow(mhwndParent, TRUE);
		}

		break;

	case kDialogVideoCapturePin:
	case kDialogVideoPreviewPin:
		TearDownGraph();

		switch(dlg) {
		case kDialogVideoCapturePin:
			{
				// For some strange reason, the Adaptec GameBridge 1.00 drivers specify two property
				// pages for the video capture pin, one of which is CLSID_AudioSamplingRate. Attempting
				// to display this page causes a crash (reproduced with AMCAP). We detect this errant
				// page and specifically exclude it from being displayed.
				//
				// Interestingly, GraphEdit does NOT have this problem... but it also doesn't seem
				// to use OleCreatePropertyFrame. Either the GraphEdit guys accidentally bypassed this
				// bug with custom code, or they know something and aren't telling us.
				//
				static const GUID kInvalidVideoCapturePinGuids[]={
					{ 0x05ea6f6b, 0x3b1e, 0x4958, 0xa6, 0x8d, 0xa3, 0x7f, 0x0b, 0x6a, 0x29, 0x95 }
				};

				DisplayPropertyPages(mpRealCapturePin, mhwndParent, kInvalidVideoCapturePinGuids, sizeof(kInvalidVideoCapturePinGuids)/sizeof(kInvalidVideoCapturePinGuids[0]));
			}
			break;
		case kDialogVideoPreviewPin:
			DisplayPropertyPages(mpRealPreviewPin, mhwndParent, NULL, 0);
			break;
		}

		// Restart the filter graph.

		BuildPreviewGraph();
		if (bRestart)
			StartGraph();
		break;

	case kDialogVideoCaptureFilter:
		DisplayPropertyPages(mpCapFilt, mhwndParent, NULL, 0);
		break;
	case kDialogVideoCrossbar:
		DisplayPropertyPages(mpCrossbar, mhwndParent, NULL, 0);
		break;
	case kDialogVideoCrossbar2:
		DisplayPropertyPages(mpCrossbar2, mhwndParent, NULL, 0);
		break;
	case kDialogTVTuner:
		DisplayPropertyPages(mpTVTuner, mhwndParent, NULL, 0);
		break;
	}

	CheckForChanges();
}

namespace {
	bool VDGetDShowProcAmpPropertyFromCaptureProperty(uint32 id, VideoProcAmpProperty& dshowPropId) {
		switch(id) {
			case kPropBacklightCompensation:
				dshowPropId = VideoProcAmp_BacklightCompensation;
				return true;

			case kPropBrightness:
				dshowPropId = VideoProcAmp_Brightness;
				return true;

			case kPropColorEnable:
				dshowPropId = VideoProcAmp_ColorEnable;
				return true;

			case kPropContrast:
				dshowPropId = VideoProcAmp_Contrast;
				return true;

			case kPropGain:
				dshowPropId = VideoProcAmp_Gain;
				return true;

			case kPropGamma:
				dshowPropId = VideoProcAmp_Gamma;
				return true;

			case kPropSaturation:
				dshowPropId = VideoProcAmp_Saturation;
				return true;

			case kPropHue:
				dshowPropId = VideoProcAmp_Hue;
				return true;

			case kPropSharpness:
				dshowPropId = VideoProcAmp_Sharpness;
				return true;

			case kPropWhiteBalance:
				dshowPropId = VideoProcAmp_WhiteBalance;
				return true;

			default:
				return false;
		}
	}
}

bool VDCaptureDriverDS::IsPropertySupported(uint32 id) {
	if (!mpVideoProcAmp)
		return false;

	VideoProcAmpProperty prop;
	if (!VDGetDShowProcAmpPropertyFromCaptureProperty(id, prop))
		return false;

	long minVal, maxVal, steppingDelta, defaultVal, capsFlags;
	HRESULT hr = mpVideoProcAmp->GetRange(prop, &minVal, &maxVal, &steppingDelta, &defaultVal, &capsFlags);
	return SUCCEEDED(hr);
}

sint32 VDCaptureDriverDS::GetPropertyInt(uint32 id, bool *pAutomatic) {
	VideoProcAmpProperty prop;
	if (!mpVideoProcAmp || !VDGetDShowProcAmpPropertyFromCaptureProperty(id, prop))
		return false;

	long lValue, lFlags;
	HRESULT hr = mpVideoProcAmp->Get(prop, &lValue, &lFlags);

	if (FAILED(hr))
		lValue = lFlags = 0;

	if (pAutomatic)
		*pAutomatic = (lFlags == VideoProcAmp_Flags_Auto);

	return lValue;
}

void VDCaptureDriverDS::SetPropertyInt(uint32 id, sint32 value, bool automatic) {
	VideoProcAmpProperty prop;
	if (!mpVideoProcAmp || !VDGetDShowProcAmpPropertyFromCaptureProperty(id, prop))
		return;

	HRESULT hr = mpVideoProcAmp->Set(prop, value, automatic ? VideoProcAmp_Flags_Auto : VideoProcAmp_Flags_Manual);
	VDASSERT(SUCCEEDED(hr));
}

void VDCaptureDriverDS::GetPropertyInfoInt(uint32 id, sint32& minVal, sint32& maxVal, sint32& step, sint32& defaultVal, bool& automatic, bool& manual) {
	minVal = maxVal = 0;
	step = 1;
	defaultVal = 0;
	automatic = false;
	manual = false;

	VideoProcAmpProperty prop;
	if (!mpVideoProcAmp || !VDGetDShowProcAmpPropertyFromCaptureProperty(id, prop))
		return;

	long lMinVal, lMaxVal, lSteppingDelta, lDefaultVal, lCapsFlags;
	HRESULT hr = mpVideoProcAmp->GetRange(prop, &lMinVal, &lMaxVal, &lSteppingDelta, &lDefaultVal, &lCapsFlags);
	if (SUCCEEDED(hr)) {
		minVal = lMinVal;
		maxVal = lMaxVal;
		step = lSteppingDelta;
		defaultVal = lDefaultVal;
		automatic = (lCapsFlags & VideoProcAmp_Flags_Auto) != 0;
		manual = (lCapsFlags & VideoProcAmp_Flags_Manual) != 0;
	}
}

bool VDCaptureDriverDS::CaptureStart() {
	if (VDINLINEASSERTFALSE(mCaptureThread)) {
		CloseHandle(mCaptureThread);
		mCaptureThread = NULL;
	}
	HANDLE hProcess = GetCurrentProcess();
	if (!DuplicateHandle(hProcess, GetCurrentThread(), hProcess, &mCaptureThread, 0, FALSE, DUPLICATE_SAME_ACCESS))
		return false;

	// there had better not be update locks when we're trying to start a capture!
	VDASSERT(!mUpdateLocks);

	// switch to a capture graph, but don't start it.
	if (BuildCaptureGraph()) {

		// cancel default handling for EC_REPAINT, otherwise
		// the Video Renderer will start sending back extra requests
		mpMediaEventEx->CancelDefaultHandling(EC_REPAINT);

		// kick the sample grabbers and go!!
		mpVideoGrabber->SetCallback(&mVideoCallback, 0);

		if (mpAudioGrabber)
			mpAudioGrabber->SetCallback(&mAudioCallback, 0);

		mCaptureStart = VDGetAccurateTick();
		mCaptureStopQueued = false;

		// Kick the graph into PAUSED state.
		
		HRESULT hr = mpGraphControl->Pause();

		if (SUCCEEDED(hr) && (!mpCB || mpCB->CapEvent(kEventPreroll, 0))) {

			// reset capture start time in case there was a preroll dialog
			mCaptureStart = VDGetAccurateTick();

			bool success = false;
			if (mpCB) {
				try {
					mpCB->CapBegin(0);
					success = true;
				} catch(MyError& e) {
					MyError *p = new MyError;
					p->TransferFrom(e);
					delete mpCaptureError.xchg(p);
				}
			}
			if (success && StartGraph())
				return true;
		}

		mGraphStateLock.Wait();
		mpGraphControl->Stop();		// Have to do this because our graph state flag doesn't track PAUSED. Okay to fail.
		mGraphStateLock.Post();

		if (mpCB)
			mpCB->CapEnd(mpCaptureError ? mpCaptureError : NULL);

		delete mpCaptureError.xchg(NULL);

		if (mpAudioGrabber)
			mpAudioGrabber->SetCallback(NULL, 0);

		mpVideoGrabber->SetCallback(NULL, 0);
	}

	mpMediaEventEx->RestoreDefaultHandling(EC_REPAINT);

	UpdateDisplay();
	return false;
}

void VDCaptureDriverDS::CaptureStop() {
	if (mCaptureThread) {
		mCaptureStopQueued = true;

		StopGraph();

		if (mpVideoGrabber)
			mpVideoGrabber->SetCallback(NULL, 0);

		mpMediaEventEx->RestoreDefaultHandling(EC_REPAINT);

		if (mpCB) {
			mpCB->CapEnd(mpCaptureError ? mpCaptureError : NULL);
		}
		delete mpCaptureError.xchg(NULL);

		if (mCaptureThread) {
			CloseHandle(mCaptureThread);
			mCaptureThread = NULL;
		}

		mCaptureStopQueued = false;

		// Switch to a preview graph.

		UpdateDisplay();
	}
}

void VDCaptureDriverDS::CaptureAbort() {
	CaptureStop();
}

bool VDCaptureDriverDS::GetDisableClockForPreview() {
	return mbDisableClockForPreview;
}

void VDCaptureDriverDS::SetDisableClockForPreview(bool enabled) {
	mbDisableClockForPreview = enabled;
}

bool VDCaptureDriverDS::GetForceAudioRendererClock() {
	return mbForceAudioRendererClock;
}

void VDCaptureDriverDS::SetForceAudioRendererClock(bool enabled) {
	mbForceAudioRendererClock = enabled;
}

void VDCaptureDriverDS::UpdateDisplay() {
	if (mUpdateLocks)
		mbUpdatePending = true;
	else {
		mbUpdatePending = false;

		VDVERIFY(BuildPreviewGraph() && StartGraph());
	}
}

bool VDCaptureDriverDS::StopGraph() {
	if (!mbGraphActive)
		return true;

	mbGraphActive = false;
	mbStartPending = false;

#ifdef _DEBUG
	uint32 startTime = VDGetAccurateTick();
	VDDEBUG("Riza/CapDShow: Filter graph stopping...\n");
#endif

	mGraphStateLock.Wait();
	HRESULT hr = mpGraphControl->Stop();
	mGraphStateLock.Post();

#ifdef _DEBUG
	VDDEBUG("Riza/CapDShow: Filter graph stopped in %d ms.\n", VDGetAccurateTick() - startTime);
#endif

	return SUCCEEDED(hr);
}

bool VDCaptureDriverDS::StartGraph() {
	if (mbGraphActive)
		return true;

	mVideoCallback.SetFrameCount(0);

	if (mUpdateLocks) {
		mbStartPending = true;
		return true;
	}

	mbStartPending = false;

	return mbGraphActive = SUCCEEDED(mpGraphControl->Run());
}

//	BuildPreviewGraph()
//
//	This routine builds the preview part of a graph.  It turns out that
//	if you try to leave the Capture pin connected, VFW drivers may not
//	send anything over their Preview pin (@#*&$*($).
//
//	Usually, this simply involves creating a video renderer and slapping
//	it on the Preview pin.
//
bool VDCaptureDriverDS::BuildPreviewGraph() {
	mVideoCallback.SetChannel(-1);
	mAudioCallback.SetChannel(-2);
	if (!BuildGraph(mDisplayMode == kDisplayAnalyze, mbAudioCaptureEnabled && mbAudioAnalysisEnabled))
		return false;

	if (mpAudioGrabber && mbAudioCaptureEnabled && mbAudioAnalysisEnabled)
		mpAudioGrabber->SetCallback(&mAudioCallback, 0);

	if (mpVideoGrabber && mDisplayMode == kDisplayAnalyze)
		mpVideoGrabber->SetCallback(&mVideoCallback, 0);

	return true;
}

//	BuildCaptureGraph()
//
//	This routine builds the capture part of the graph:
//
//	* Check if the capture filter only has a capture pin.  If so, insert
//	  a smart tee filter to make a fake preview pin.
//
//	* Connect a sample grabber and then a null renderer onto the capture
//	  pin.
//
//	* Render the preview pin.
//
bool VDCaptureDriverDS::BuildCaptureGraph() {
	if (BuildGraph(true, mbAudioCaptureEnabled)) {
		mVideoCallback.SetChannel(0);
		mAudioCallback.SetChannel(1);
		return true;
	}

	return false;
}

bool VDCaptureDriverDS::BuildGraph(bool bNeedCapture, bool bEnableAudio) {
	IPinPtr pCapturePin = mpRealCapturePin;
	IPinPtr pPreviewPin = mpRealPreviewPin;
	IPinPtr	pVideoPortPin = mpCapFiltVideoPortPin;
	HRESULT hr;

	VDASSERT(!mUpdateLocks);

	// Tear down existing graph.
	TearDownGraph();

	// Check if the audio filter is in the filter graph, and if so, rip
	// it out of the graph. The reason we have to do so is that the DivX
	// Decoder filter is broken and can connect to MEDIASUBTYPE_AnalogAudio
	// inputs on the audio capture filter, completely screwing up the
	// graph building process. This was observed with the DivX 5.2.1
	// DirectShow decoder when rendering the preview pin of the Plextor
	// PX-M402U capture filter with an SBLive! Capture filter already in
	// the graph.

	if (mpAudioCapFilt) {
		if (VDIsFilterInGraphDShow(mpAudioCapFilt))
			mpGraphBuilder->RemoveFilter(mpAudioCapFilt);
	}

	///////////////////////////////////////////////////////////////////////
	//
	// VIDEO PORTION
	//
	///////////////////////////////////////////////////////////////////////
	VDASSERT(!mpAudioPin || !VDIsPinConnectedDShow(mpAudioPin));

	// Check if we need to reattach a transform filter (it can unattach
	// on a format change).
	if (mpCapTransformFilt) {
		IPinPtr pPinTest;

		if (VFW_E_NOT_CONNECTED == mpRealCapturePin->ConnectedTo(~pPinTest)) {
			// Reconnect transform filter input to capture pin
			IPinPtr pPinTIn;
			DS_VERIFY(mpCapGraphBuilder2->FindPin(mpCapTransformFilt, PINDIR_INPUT, NULL, NULL, TRUE, 0, ~pPinTIn), "find transform filter input");
			DS_VERIFY(mpGraphBuilder->Connect(mpShadowedRealCapturePin, pPinTIn), "reconnect capture -> transform");
		}
	}

	if (bNeedCapture || mDisplayMode == kDisplaySoftware) {
		// Create a sample grabber.
		IBaseFilterPtr pVideoPullFilt;

		DS_VERIFY(pVideoPullFilt.CreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER), "create sample grabber");
		DS_VERIFY(mpGraphBuilder->AddFilter(pVideoPullFilt, L"Video pulldown"), "add sample grabber");

		mExtraFilters.push_back(pVideoPullFilt);

		// Set the sample grabber to continuous mode.
		//
		// NOTE: In preview mode we use this sample grabber to force the
		// stream to a particular format. We don't actually install a
		// callback.

		DS_VERIFY(pVideoPullFilt->QueryInterface(IID_ISampleGrabber, (void **)~mpVideoGrabber), "find sample grabber if");
		DS_VERIFY(mpVideoGrabber->SetOneShot(FALSE), "switch sample grabber to continuous");

		AM_MEDIA_TYPE vamtDummy;

		vamtDummy.majortype	= MEDIATYPE_Video;
		vamtDummy.subtype	= GUID_NULL;
		vamtDummy.bFixedSizeSamples = FALSE;
		vamtDummy.bTemporalCompression = FALSE;
		vamtDummy.lSampleSize	= 0;
		vamtDummy.formattype	= FORMAT_VideoInfo;
		vamtDummy.pUnk		= NULL;
		vamtDummy.cbFormat	= 0;
		vamtDummy.pbFormat	= NULL;

		DS_VERIFY(mpVideoGrabber->SetMediaType(&vamtDummy), "set video sample grabber format");

		// Attach the sink to the grabber, and the grabber to the capture pin.

		IPinPtr pPinSGIn, pPinSGOut;

		DS_VERIFY(mpCapGraphBuilder2->FindPin(pVideoPullFilt, PINDIR_INPUT, NULL, NULL, TRUE, 0, ~pPinSGIn), "find sample grabber input");
		DS_VERIFY(mpCapGraphBuilder2->FindPin(pVideoPullFilt, PINDIR_OUTPUT, NULL, NULL, TRUE, 0, ~pPinSGOut), "find sample grabber output");
		DS_VERIFY(mpGraphBuilder->Connect(pCapturePin, pPinSGIn), "connect capture -> grabber");

		pCapturePin = pPinSGOut;
	}
	VDASSERT(!mpAudioPin || !VDIsPinConnectedDShow(mpAudioPin));

	// Render the preview part.
	//
	//
	// Software configuration:
	//
	// +----------------+~capture  +--------------+     +----------+
	// |                |--------->|sample grabber|---->| renderer |
	// | capture filter |          +--------------+     +----------+
	// |                |->
	// +----------------+preview
	//
	//
	// Hardware configuration:
	// +----------------+~capture  +--------------+
	// |                |--------->|sample grabber|---------------------->
	// | capture filter |          +--------------+     +----------+
	// |                |------------------------------>| renderer |
	// +----------------+preview                        +----------+
	//
	// Note that we use the video port pin preferentially to the preview
	// pin.

	VDASSERT(pPreviewPin != mpAudioPin);
	VDASSERT(pCapturePin != mpAudioPin);

	switch(mDisplayMode) {
	case kDisplayHardware:
		// Render the preview pin if it exists, otherwise fall through.
		if (pVideoPortPin) {
			// No need to handle this here, as we always render the VP pin below.
			break;
		} else if (pPreviewPin) {
			DS_VERIFY(mpGraphBuilder->Render(pPreviewPin), "render preview pin (hardware display)");
			pPreviewPin = NULL;
			break;
		}
	case kDisplaySoftware:
		// In software mode we force the rendering path to use the same format that
		// would be used for capture. This is already done for us above, so we
		// simply add the renderer.
		//
		// Note that while it might seem like a good idea to use a Smart Tee to rip off the
		// timestamps, this doesn't work since it causes the video to play at maximum speed,
		// causing horrible stuttering on the PX-M402U.
		//
		DS_VERIFY(mpGraphBuilder->Render(pCapturePin), "render capture pin (hardware display)");
		pCapturePin = NULL;
		break;
	}

	// Render the video port pin. We're supposed to do this in any case
	// according to the DirectShow docs for Video Port pins. We HAVE to do
	// this regardless of whether we actually want to use the overlay as
	// otherwise the ATI All-in-Wonder RADEON driver locks up and zombies
	// our process.

	if (pVideoPortPin) {
		DS_VERIFY(mpGraphBuilder->Render(pVideoPortPin), "render video port pin");
		pVideoPortPin = NULL;
	}

	// Add the audio filter back into the graph, if it exists.
	if (mpAudioCapFilt) {
		DS_VERIFY(mpGraphBuilder->AddFilter(mpAudioCapFilt, L"Audio capture"), "add audio capture filter to graph");
	}

	VDASSERT(!mpAudioPin || !VDIsPinConnectedDShow(mpAudioPin));

	// Terminate the capture pin with a null renderer if it has not
	// already been terminated.
	//
	//            +-------------+
	//   -------->|null renderer|
	//            +-------------+
	//
	if (pCapturePin) {
		IBaseFilterPtr pNullRenderer;
		IPinPtr pPinNRIn;

		DS_VERIFY(pNullRenderer.CreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER), "create null renderer");
		DS_VERIFY(mpGraphBuilder->AddFilter(pNullRenderer, L"Video sink"), "add null renderer");
		mExtraFilters.push_back(pNullRenderer);
		DS_VERIFY(mpCapGraphBuilder2->FindPin(pNullRenderer, PINDIR_INPUT, NULL, NULL, TRUE, 0, ~pPinNRIn), "find null renderer input");
		DS_VERIFY(mpGraphBuilder->Connect(pCapturePin, pPinNRIn), "connect grabber -> sink");
		pCapturePin = NULL;
	}

	///////////////////////////////////////////////////////////////////////
	//
	// AUDIO PORTION
	//
	///////////////////////////////////////////////////////////////////////

	IPinPtr pAudioPin = mpAudioPin;

	VDASSERT(!pAudioPin || !VDIsPinConnectedDShow(pAudioPin));
	if (bNeedCapture || (bEnableAudio && mbAudioAnalysisEnabled)) {
		// We need to do the audio now.

		if (pAudioPin && bEnableAudio) {
			IPinPtr pPinSGIn, pPinSGOut;
			IBaseFilterPtr pAudioPullFilt;

			DS_VERIFY(pAudioPullFilt.CreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER), "create sample grabber");
			DS_VERIFY(mpGraphBuilder->AddFilter(pAudioPullFilt, L"Audio pulldown"), "add sample grabber");
			mExtraFilters.push_back(pAudioPullFilt);

			// Set the sample grabber to continuous mode.

			DS_VERIFY(pAudioPullFilt->QueryInterface(IID_ISampleGrabber, (void **)~mpAudioGrabber), "find sample grabber if");

			if (mAudioFormat.empty())
				return false;

			VDWaveFormatAsDShowMediaType amt(mAudioFormat.data(), mAudioFormat.size());

			DS_VERIFY(mpAudioGrabber->SetMediaType(&amt), "set media type");
			DS_VERIFY(mpAudioGrabber->SetOneShot(FALSE), "switch sample grabber to continuous");

			// Attach the grabber to the capture pin.

			DS_VERIFY(mpCapGraphBuilder2->FindPin(pAudioPullFilt, PINDIR_INPUT, NULL, NULL, TRUE, 0, ~pPinSGIn), "find sample grabber input");
			DS_VERIFY(mpCapGraphBuilder2->FindPin(pAudioPullFilt, PINDIR_OUTPUT, NULL, NULL, TRUE, 0, ~pPinSGOut), "find sample grabber output");

			// If audio analysis is enabled, try to keep the latency down to at least 1/30th of a second.
			if (mbAudioAnalysisEnabled) {
				ALLOCATOR_PROPERTIES allocProp;

				allocProp.cbAlign = -1;
				allocProp.cbBuffer = mAudioFormat->nAvgBytesPerSec/30;
				if (!allocProp.cbBuffer)
					allocProp.cbBuffer = 1;
				allocProp.cbBuffer += mAudioFormat->nBlockAlign - 1;
				allocProp.cbBuffer -= allocProp.cbBuffer % mAudioFormat->nBlockAlign;
				allocProp.cbPrefix = -1;
				allocProp.cBuffers = -1;

				IAMBufferNegotiation *pBN;

				if (SUCCEEDED(pAudioPin->QueryInterface(IID_IAMBufferNegotiation, (void **)&pBN))) {
					if (FAILED(pBN->SuggestAllocatorProperties(&allocProp))) {
						VDDEBUG("Riza/DShow: Warning: Unable to suggest buffer size of %u bytes to audio capture pin\n", (unsigned)allocProp.cbBuffer);
					}
					pBN->Release();
				}

				if (SUCCEEDED(pPinSGIn->QueryInterface(IID_IAMBufferNegotiation, (void **)&pBN))) {
					if (FAILED(pBN->SuggestAllocatorProperties(&allocProp))) {
						VDDEBUG("Riza/DShow: Warning: Unable to suggest buffer size of %u bytes to sample grabber pin\n", (unsigned)allocProp.cbBuffer);
					}
					pBN->Release();
				}
			}

			DS_VERIFY(mpGraphBuilder->Connect(pAudioPin, pPinSGIn), "connect capture -> grabber");
			pAudioPin = pPinSGOut;
			VDASSERT(!VDIsPinConnectedDShow(pAudioPin));
		}
	}

	// Terminate the audio path with a null renderer or a sound output. We do this
	// even if we are just previewing, because the Plextor PX-M402U doesn't output
	// frames reliably otherwise.
	bool bUseDefaultClock = true;

	if (pAudioPin) {
		VDASSERT(!VDIsPinConnectedDShow(pAudioPin));

		if (mbAudioPlaybackEnabled) {
			HRESULT hrRender = mpGraphBuilder->Render(pAudioPin);

			// Reset the filter graph clock. We have to do this because when we
			// create a capture graph a different filter may end up being the
			// clock. For some reason the DirectSound Renderer refuses to play
			// if we try using the system clock. (SAA713x)
			if (mbForceAudioRendererClock) {
				IMediaFilterPtr pGraphMF;

				if (SUCCEEDED(mpGraphBuilder->QueryInterface(IID_IMediaFilter, (void **)~pGraphMF))) {
					IPinPtr pAudioPinConnect;
					if (SUCCEEDED(pAudioPin->ConnectedTo(~pAudioPinConnect))) {
						PIN_INFO pi;

						if (SUCCEEDED(pAudioPinConnect->QueryPinInfo(&pi))) {
							bUseDefaultClock = !SetClockFromDownstream(pi.pFilter, pGraphMF);
							pi.pFilter->Release();
						}
					}
				}
			}

			if (SUCCEEDED(hrRender))
				pAudioPin = NULL;
		}

		if (pAudioPin) {
			IPinPtr pPinRIn;
			IBaseFilterPtr pRenderer;
			VDASSERT(!VDIsPinConnectedDShow(pAudioPin));
			DS_VERIFY(pRenderer.CreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER), "create audio null renderer");
			DS_VERIFY(mpGraphBuilder->AddFilter(pRenderer, L"Audio sink"), "add null renderer");
			mExtraFilters.push_back(pRenderer);
			DS_VERIFY(mpCapGraphBuilder2->FindPin(pRenderer, PINDIR_INPUT, NULL, NULL, TRUE, 0, ~pPinRIn), "find audio renderer input");
			DS_VERIFY(mpGraphBuilder->Connect(pAudioPin, pPinRIn), "connect audio null renderer");
		}
	}

	// If the capture filter has an audio pin that's not being used, terminate that too.
	if (mpRealAudioPin && mpRealAudioPin != mpAudioPin) {
		IPinPtr pPinRIn;
		IBaseFilterPtr pRenderer;
		DS_VERIFY(pRenderer.CreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC_SERVER), "create audio null renderer");
		DS_VERIFY(mpGraphBuilder->AddFilter(pRenderer, L"Audio sink"), "add null renderer");
		mExtraFilters.push_back(pRenderer);
		DS_VERIFY(mpCapGraphBuilder2->FindPin(pRenderer, PINDIR_INPUT, NULL, NULL, TRUE, 0, ~pPinRIn), "find audio renderer input");
		DS_VERIFY(mpGraphBuilder->Connect(mpRealAudioPin, pPinRIn), "connect audio null renderer to real audio cap pin");
	}

	// Set the graph clock
	if (mbDisableClockForPreview && !bNeedCapture) {
		IMediaFilterPtr pGraphMF;

		if (SUCCEEDED(mpGraphBuilder->QueryInterface(IID_IMediaFilter, (void **)~pGraphMF)))
			pGraphMF->SetSyncSource(NULL);
	} else if (bUseDefaultClock) {
		IMediaFilterPtr pGraphMF;

		if (SUCCEEDED(mpGraphBuilder->QueryInterface(IID_IMediaFilter, (void **)~pGraphMF))) {
			IReferenceClockPtr pGraphClock;

			if (SUCCEEDED(pGraphClock.CreateInstance(CLSID_SystemClock, NULL, CLSCTX_INPROC_SERVER)))
				pGraphMF->SetSyncSource(pGraphClock);
		}
	}

	// Dump the graph to stdout.
	VDDumpFilterGraphDShow(mpGraphBuilder);

	// Check for a window and return.
	CheckForWindow();

	// Broadcast events if values changed.
	CheckForChanges();

	return true;
}

//
//	TearDownGraph()
//
//	This rips up everything in the filter graph beyond the capture filter.
//
void VDCaptureDriverDS::TearDownGraph() {
	StopGraph();

	// drop pointers to graph components
	if (mpAudioGrabber)
		mpAudioGrabber->SetCallback(NULL, 0);

	mpVideoGrabber			= NULL;
	mpVideoWindow			= NULL;
	mVideoWindows.clear();
	if (mpVideoQualProp) {
		mpVideoQualProp->Release();
		mpVideoQualProp = NULL;
	}

	mpAudioGrabber = NULL;

	// reset capture clock
	IMediaFilterPtr pGraphMF;

	if (SUCCEEDED(mpGraphBuilder->QueryInterface(IID_IMediaFilter, (void **)~pGraphMF))) {
		pGraphMF->SetSyncSource(NULL);
	}

	// destroy downstreams
	DestroySubgraph(mpGraphBuilder, mpCapFilt, mpCapSplitFilt, mpCapTransformFilt);
	DestroySubgraph(mpGraphBuilder, mpAudioCapFilt, NULL, NULL);

	// yank any loose filters
	while(!mExtraFilters.empty()) {
		IBaseFilterPtr filt = mExtraFilters.back();
		mExtraFilters.pop_back();

		FILTER_INFO finfo;
		if (SUCCEEDED(filt->QueryFilterInfo(&finfo))) {
			if (finfo.pGraph) {
				finfo.pGraph->RemoveFilter(filt);
				finfo.pGraph->Release();
			}
		}
	}

	VDASSERT(!mpRealAudioPin || !VDIsPinConnectedDShow(mpRealAudioPin));
	VDASSERT(!mpAudioPin || !VDIsPinConnectedDShow(mpAudioPin));
}

//
//	CheckForWindow()
//
//	Look for a video window in the capture graph, and make it ours if
//	there is one.
//
void VDCaptureDriverDS::CheckForWindow() {
	if (mpVideoQualProp) {
		mpVideoQualProp->Release();
		mpVideoQualProp = NULL;
	}

	// Sweep the filter graph and pick up all video windows, along with
	// the first IVideoQualProp interface we can find.
	IEnumFiltersPtr pEnumFilters;

	mVideoWindows.clear();
	if (SUCCEEDED(mpGraphBuilder->EnumFilters(~pEnumFilters))) {
		IBaseFilterPtr pFilter;

		while(S_OK == pEnumFilters->Next(1, ~pFilter, NULL)) {
			if (!mpVideoQualProp)
				pFilter->QueryInterface(IID_IQualProp, (void **)&mpVideoQualProp);	// OK for this to fail.

			IVideoWindowPtr pVW;
			if (SUCCEEDED(pFilter->QueryInterface(IID_IVideoWindow, (void **)~pVW))) {
				// hide the video window
				pVW->put_Visible(OAFALSE);
				pVW->put_AutoShow(OAFALSE);

				// add it to our list
				mVideoWindows.push_back(IVideoWindowPtr());
				mVideoWindows.back().Swap(pVW);
			}
		}
	}

	VDDEBUG("Riza/CapDShow: %u windows found.\n", mVideoWindows.size());

	// Only overlay and preview modes use DirectShow displays
	if (mDisplayMode == kDisplayNone || mDisplayMode == kDisplayAnalyze)
		return;

	// Look for the one video window we do want to show. The order is capture, then
	// preview, then video port. We'll always have at least one if display is enabled,
	// and possibly two if we hide the video port (we always have to have it).

	IBaseFilterPtr pFilter;
	bool success = false;

	// try capture pin first
	if (mpRealCapturePin) {
		success = VDGetFilterConnectedToPinDShow(mpRealCapturePin, ~pFilter);
		if (success) {
			HRESULT hr = mpCapGraphBuilder2->FindInterface(&LOOK_DOWNSTREAM_ONLY, NULL, pFilter, IID_IVideoWindow, (void **)~mpVideoWindow);
			success = SUCCEEDED(hr);
		}
	}

	// next, try preview pin
	if (!success && mpRealPreviewPin) {
		success = VDGetFilterConnectedToPinDShow(mpRealPreviewPin, ~pFilter);
		if (success) {
			HRESULT hr = mpCapGraphBuilder2->FindInterface(&LOOK_DOWNSTREAM_ONLY, NULL, pFilter, IID_IVideoWindow, (void **)~mpVideoWindow);
			success = SUCCEEDED(hr);
		}
	}

	// try video port
	if (!success && mpCapFiltVideoPortPin) {
		success = VDGetFilterConnectedToPinDShow(mpCapFiltVideoPortPin, ~pFilter);
		if (success) {
			HRESULT hr = mpCapGraphBuilder2->FindInterface(&LOOK_DOWNSTREAM_ONLY, NULL, pFilter, IID_IVideoWindow, (void **)~mpVideoWindow);
			success = SUCCEEDED(hr);
		}
	}

	// okay... use any others
	if (!success && !mVideoWindows.empty())
		mpVideoWindow = mVideoWindows.front();

	// if we have a video window, configure it
	if (mpVideoWindow) {
		long styles;

		mpVideoWindow->put_Visible(OAFALSE);
		mpVideoWindow->put_AutoShow(mbDisplayVisible ? OATRUE : OAFALSE);

		if (SUCCEEDED(mpVideoWindow->get_WindowStyle(&styles))) {
			mpVideoWindow->put_WindowStyle(styles & ~(WS_CAPTION|WS_THICKFRAME));
		}

		// Add WS_EX_NOPARENTNOTIFY to try to avoid certain kinds of deadlocks,
		// since the video window is in a different thread than its parent.

		if (SUCCEEDED(mpVideoWindow->get_WindowStyleEx(&styles))) {
			mpVideoWindow->put_WindowStyleEx(styles | WS_EX_NOPARENTNOTIFY);
		}

		SetDisplayRect(mDisplayRect);
		mpVideoWindow->put_Owner((OAHWND)mhwndParent);
	}
}

void VDCaptureDriverDS::CheckForChanges() {
	// check for crossbar changes
	if (mpAudioCrossbar) {
		int newAudioSource = UpdateCrossbarSource(mAudioSources, mpAudioCrossbar, mAudioCrossbarOutput);

		if (mCurrentAudioSource != newAudioSource) {
			mCurrentAudioSource = newAudioSource;

			if (mpCB)
				mpCB->CapEvent(kEventAudioSourceChanged, newAudioSource);
		}
	}

	if (mpVideoCrossbar) {
		int newVideoSource = UpdateCrossbarSource(mVideoSources, mpVideoCrossbar, mVideoCrossbarOutput);

		if (mCurrentVideoSource != newVideoSource) {
			mCurrentVideoSource = newVideoSource;

			if (mpCB)
				mpCB->CapEvent(kEventVideoSourceChanged, newVideoSource);
		}
	}

	// check for video frame or format changes
	if (mpCB) {
		bool keepGoing = true;

		sint32 fp = GetFramePeriod();

		if (mTrackedFramePeriod != fp) {
			mTrackedFramePeriod = fp;

			keepGoing &= mpCB->CapEvent(kEventVideoFrameRateChanged, 0);
		}

		vdstructex<BITMAPINFOHEADER> vf;
		GetVideoFormat(vf);

		if (mTrackedVideoFormat != vf) {
			mTrackedVideoFormat = vf;

			keepGoing &= mpCB->CapEvent(kEventVideoFormatChanged, 0);
		}

		if (!keepGoing && mCaptureThread)
			CaptureStop();
	}
}

bool VDCaptureDriverDS::DisplayPropertyPages(IUnknown *ptr, HWND hwndParent, const GUID *pDisablePages, int nDisablePages) {
	if (!ptr)
		return false;

	HRESULT hr;
	ISpecifyPropertyPages *pPages;
	hr = ptr->QueryInterface(IID_ISpecifyPropertyPages, (void **)&pPages);
	if (FAILED(hr))
		return false;

	CAUUID cauuid;
	hr = pPages->GetPages(&cauuid);
	pPages->Release();

	if (FAILED(hr))
		return false;

	bool success = false;
	if (cauuid.cElems) {
		uint32 n = 0;
		for(uint32 i = 0; i < cauuid.cElems; ++i) {
			const GUID& guid = cauuid.pElems[i];
			bool copy = true;

			for(int j=0; j<nDisablePages; ++j) {
				if (guid == pDisablePages[j]) {
					copy = false;
					break;
				}
			}

			if (copy)
				cauuid.pElems[n++] = guid;
		}

		if (n) {
			if (hwndParent) {
				HRESULT hr = OleCreatePropertyFrame(hwndParent, 0, 0, NULL, 1,
					&ptr, n, cauuid.pElems, 0, 0, NULL);

				success = SUCCEEDED(hr);
			} else
				success = true;
		}
	}

	CoTaskMemFree(cauuid.pElems);

	return success;
}

int VDCaptureDriverDS::EnumerateCrossbarSources(InputSources& sources, IAMCrossbar *pCrossbar, int output) {
	// If there is an audio crossbar, scan it for sources.
	long inputs, outputs;
	int currentSourceIndex = -1;

	if (SUCCEEDED(pCrossbar->get_PinCounts(&outputs, &inputs))) {
		long currentSource = -1;

		VDVERIFY(SUCCEEDED(pCrossbar->get_IsRoutedTo(output, &currentSource)));

		for(int pin=0; pin<inputs; ++pin) {
			// get pin type
			long relatedPin;
			long physType;
			if (FAILED(pCrossbar->get_CrossbarPinInfo(TRUE, pin, &relatedPin, &physType)))
				continue;

			// check if this input can be routed to the audio output
			if (S_OK != pCrossbar->CanRoute(output, pin))
				continue;

			// check if this is the current pin
			if (pin == currentSource)
				currentSourceIndex = sources.size();

			// add entry for this pin
			sources.push_back(InputSource(pin, physType, VDStringW(VDGetNameForPhysicalConnectorTypeDShow((PhysicalConnectorType)physType))));
		}
	}

	return currentSourceIndex;
}

int VDCaptureDriverDS::UpdateCrossbarSource(InputSources& sources, IAMCrossbar *pCrossbar, int output) {
	// If there is an audio crossbar, scan it for sources.
	long inputs, outputs;
	if (SUCCEEDED(pCrossbar->get_PinCounts(&outputs, &inputs))) {
		long currentSource = -1;

		VDVERIFY(SUCCEEDED(pCrossbar->get_IsRoutedTo(output, &currentSource)));

		InputSources::const_iterator it(sources.begin()), itEnd(sources.end());
		int index = 0;
		for(; it!=itEnd; ++it, ++index) {
			const InputSource& src = *it;

			if (src.mCrossbarPin == currentSource)
				return index;
		}
	}

	return -1;
}

void VDCaptureDriverDS::DoEvents() {
	long evCode;
	LONG_PTR param1, param2;

	while(SUCCEEDED(mpMediaEventEx->GetEvent(&evCode, &param1, &param2, 0))) {
#if 0
		if (mpEventCallback)
			switch(evCode) {
			case EC_VIDEO_SIZE_CHANGED:
				mpEventCallback->EventVideoSizeChanged(LOWORD(param1), HIWORD(param1));
				break;
			}
#endif
		switch(evCode) {
		case EC_ERRORABORT:
			VDDEBUG("Cap/DShow: Error abort detected: hr=%08x (%s)\n", (int)param1, GetDXErrorName((HRESULT)param1));
			break;
		default:
			VDDEBUG("Cap/DShow: Unknown event %08x detected\n", (int)evCode);
			break;
		}

		mpMediaEventEx->FreeEventParams(evCode, param1, param2);
	}
}

LRESULT CALLBACK VDCaptureDriverDS::StaticMessageSinkWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	if (msg == WM_NCCREATE)
		SetWindowLongPtr(hwnd, 0, (LONG_PTR)((LPCREATESTRUCT)lParam)->lpCreateParams);

	VDCaptureDriverDS *pThis = (VDCaptureDriverDS *)GetWindowLongPtr(hwnd, 0);

	return pThis ? pThis->MessageSinkWndProc(hwnd, msg, wParam, lParam) : DefWindowProc(hwnd, msg, wParam, lParam);
}

LRESULT VDCaptureDriverDS::MessageSinkWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	if (msg == WM_APP)
		DoEvents();
	else if (msg == WM_APP+1)
		CaptureStop();
	else {
		VideoWindows::const_iterator it(mVideoWindows.begin()), itEnd(mVideoWindows.end());
		for(; it!=itEnd; ++it) {
			const IVideoWindowPtr& p = *it;

			p->NotifyOwnerMessage((OAHWND)hwnd, msg, wParam, lParam);
		}
	}

	return DefWindowProc(hwnd, msg, wParam, lParam);
}

bool VDCaptureDriverDS::CapTryEnterCriticalSection() {
	return mGraphStateLock.TryWait();
}

void VDCaptureDriverDS::CapProcessData(int stream, const void *data, uint32 size, sint64 timestamp, bool key) {
	if (mpCaptureError)
		return;

	if (mpCB) {
		sint64 reltime = (sint64)(VDGetAccurateTick() - mCaptureStart) * 1000;

		try {
			if (!mCaptureStopQueued) {
				if (mCaptureThread) {
					if (!mpCB->CapEvent(kEventCapturing, 0)) {
						if (!mCaptureStopQueued.xchg(1))
							PostMessage(mhwndEventSink, WM_APP+1, 0, 0);

						goto skip_processing;
					}
				}

				mpCB->CapProcessData(stream, data, size, timestamp, key, reltime);
skip_processing:
				;
			}
		} catch(MyError& e) {
			MyError *e2 = new MyError;
			e2->TransferFrom(e);
			delete mpCaptureError.xchg(e2);

			if (!mCaptureStopQueued.xchg(1))
				PostMessage(mhwndEventSink, WM_APP+1, 0, 0);
		}
	}
}

void VDCaptureDriverDS::CapLeaveCriticalSection() {
	mGraphStateLock.Post();
}

///////////////////////////////////////////////////////////////////////////
//
//	capture system: DirectShow
//
///////////////////////////////////////////////////////////////////////////

class VDCaptureSystemDS : public IVDCaptureSystem {
public:
	VDCaptureSystemDS();
	~VDCaptureSystemDS();

	void EnumerateDrivers();

	int GetDeviceCount();
	const wchar_t *GetDeviceName(int index);

	IVDCaptureDriver *CreateDriver(int deviceIndex);

protected:
	int mDriverCount;

	tDeviceVector mVideoDevices;
};

IVDCaptureSystem *VDCreateCaptureSystemDS() {
	return new VDCaptureSystemDS;
}

VDCaptureSystemDS::VDCaptureSystemDS()
	: mDriverCount(0)
{
	CoInitialize(NULL);
}

VDCaptureSystemDS::~VDCaptureSystemDS() {
	mVideoDevices.clear();		// must clear this first before COM goes away
	CoUninitialize();
}

void VDCaptureSystemDS::EnumerateDrivers() {
	mDriverCount = 0;
	mVideoDevices.clear();

	Enumerate(mVideoDevices, CLSID_VideoInputDeviceCategory);
	mDriverCount = mVideoDevices.size();
}

int VDCaptureSystemDS::GetDeviceCount() {
	return mDriverCount;
}

const wchar_t *VDCaptureSystemDS::GetDeviceName(int index) {
	if ((unsigned)index >= (unsigned)mDriverCount)
		return NULL;

	return mVideoDevices[index].second.c_str();
}

IVDCaptureDriver *VDCaptureSystemDS::CreateDriver(int index) {
	if ((unsigned)index >= (unsigned)mDriverCount)
		return NULL;

	return new VDCaptureDriverDS(mVideoDevices[index].first);
}
